#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
基于分钟级数据的策略回测系统
功能：
1. 历史数据回测（基于分钟级数据）
2. 策略表现分析  
3. 风险指标计算
4. 详细交易记录
5. 可视化报告生成
6. 自定义时间区间
7. 智能技术指标卖出

快速使用示例：
# 1. 运行完整交互式回测
python 分时策略.py

# 2. 命令行参数调用
python 分时策略.py --date 20250920 --stock 000001.SZ

# 3. 程序化调用示例
backtest = MinuteStrategyBacktest(initial_capital=100000)
results = backtest.run_backtest(
    strategy_file="突破5日线",
    stock_list=['股票代码'],  # 输入6位代码即可，系统会自动添加.SH或.SZ
    start_date='20250101',
    end_date='20251103'  # 示例：也可以用 datetime.now().strftime('%Y%m%d') 获取当前日期
)
backtest.plot_performance("report.png")
"""
import logging
import numpy as np
import pandas as pd
from datetime import datetime, timedelta
from xtquant import xtdata
import json
import os
import sys
import time

# 项目模块
from position_management.position_manager import PositionInfo
from utils.trade_analyzer import TradeAnalyzer
from utils.signal_formatter import TradeFormatter
import argparse
from typing import Dict, List, Tuple, Optional
from config_loader import TECHNICAL_PATTERNS_CONFIG  # 添加技术形态配置导入
import matplotlib.pyplot as plt
import matplotlib.dates as mdates
from matplotlib import rcParams
import mplfinance as mpf
import warnings
import pickle  # 添加pickle模块用于数据缓存

# 忽略matplotlib的字体警告
warnings.filterwarnings("ignore", category=UserWarning, module="matplotlib")
warnings.filterwarnings("ignore", category=UserWarning, module="tkinter")

# 设置中文字体
rcParams['font.sans-serif'] = ['SimHei', 'Microsoft YaHei', 'DejaVu Sans', 'sans-serif']
rcParams['axes.unicode_minus'] = False
plt.rcParams['font.sans-serif'] = ['SimHei', 'Microsoft YaHei', 'DejaVu Sans', 'sans-serif']
plt.rcParams['axes.unicode_minus'] = False

# 增强字体支持，避免警告
plt.rcParams['font.family'] = 'sans-serif'
plt.rcParams['font.size'] = 12

# 配置日志
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(levelname)s - %(message)s',
    datefmt='%Y-%m-%d %H:%M:%S'
)
logger = logging.getLogger('MinuteBacktest')

class MinuteStrategyBacktest:
    """分时策略回测系统"""
    
    def __init__(self, initial_capital: float = None, commission_rate: float = None):
        """
        初始化回测系统
        :param initial_capital: 初始资金（默认从配置文件读取）
        :param commission_rate: 手续费率（默认从配置文件读取）
        """
        # 从配置文件加载（延迟导入避免循环依赖）
        from config_loader import BACKTEST_CONFIG, MARKET_CAP_THRESHOLD
        from utils.slippage_calculator import SlippageCalculator
        from signal_validation import EnhancedSellSignalChecker
        
        self.backtest_config = BACKTEST_CONFIG
        self.initial_capital = initial_capital or self.backtest_config['initial_capital']
        self.commission_rate = commission_rate or self.backtest_config['commission_rate']
        self.current_capital = self.initial_capital
        # 数据下载配置（从配置文件读取）
        self.dividend_type = self.backtest_config.get('dividend_type', 'front')
        self.incremental_download = self.backtest_config.get('incremental_download', True)
        
        # 初始化滑点计算器
        self.slippage_calculator = SlippageCalculator(self.backtest_config)
        self.slippage_calculator.set_market_cap_threshold(MARKET_CAP_THRESHOLD)
        
        # 初始化信号验证系统
        from validation_config_loader import VALIDATION_RULES
        self.signal_checker = EnhancedSellSignalChecker(config=VALIDATION_RULES)
        logger.info("信号验证系统已启用（使用 validation_rules.yaml 配置）")
        
        # 初始化止损服务（独立模块）
        from utils.stop_loss_service import create_stop_loss_service
        self.stop_loss_service = create_stop_loss_service()
        logger.info(f"止损服务已初始化: enabled={self.stop_loss_service.is_enabled()}")
        
        # 加载技术形态增强配置（在初始化时读取，避免运行时失败）
        self._load_tech_enhancement_config(TECHNICAL_PATTERNS_CONFIG)

        # 日志去重集合（减少重复输出）
        self.logged_filtered_signals = {}  # {(date_str, stock, condition_key): {'count': int, 'last_time': float}}
        self.logged_t1_intercepts = set()  # {(date_str, stock)} 用于T+1拦截日志去重
        self.logged_confirmed_signals = {}  # {(date_str, stock, reason_hash): count} 用于确认信号计数
        
        # 技术形态引擎（延迟初始化，在策略加载后）
        self.technical_engine = None
        self.technical_enabled = False
        
        # 交易记录
        self.trades = []
        self.minute_returns = {}
        self.positions = {}  # 当前持仓 {stock: total_shares}
        self.position_batches = {}  # 批次追踪详细信息
        self.daily_capital_usage = []  # 每日资金占用记录 [{date: str, capital_used: float}]
        # 数据结构: {
        #   stock: {
        #     'batches': [{'batch_num': 1, 'shares': 100, 'price': 106.44, 'time': '...', 'amount': 10645.23}, ...],
        #     'target_position': 0.12,  # 目标仓位
        #     'current_batch': 1,  # 当前批次号
        #     'first_batch_ratio': 0.084,  # 第一批比例
        #     'first_buy_price': 106.44,  # 首次买入价（用于加仓判断）
        #     'first_buy_time': '2025-01-01 09:30:00'  # 首次买入时间
        #   }
        # }
        self.max_positions = self.backtest_config['max_positions']  # 最大持仓数量（必须在config.yaml中配置）
        # 注意：仓位计算现在由PositionCalculator动态计算，此参数仅作为异常时的fallback
        self.single_position_ratio = self.backtest_config['fallback_position_ratio']  # fallback默认仓位（必须在config.yaml中配置）
        
        # 性能指标
        self.metrics = {}
        
        # 数据缓存
        self.stock_data = {}  # 分钟级数据
        self.daily_data = {}  # 日线数据（用于均线计算）
        
        # 回测日志文件（用于记录所有买入/卖出操作）
        self.backtest_log_file = None
        self._init_backtest_log_file()
        
        # 策略相关
        self.strategy_class = None
        self.strategy_module = None
        self.strategy_wrapper = None
        
        logger.info(f"分钟级回测系统初始化完成")
        logger.info(f"  初始资金: {self.initial_capital:,.2f}元")
        logger.info(f"  手续费率: {self.commission_rate*10000:.2f}‱")
        logger.info(f"  滑点模型: 动态分层滑点（市值+波动率）")
    
    def _init_backtest_log_file(self):
        """初始化回测日志文件"""
        try:
            from datetime import datetime as dt
            import os
            
            log_dir = "backtest_logs"
            if not os.path.exists(log_dir):
                os.makedirs(log_dir)
            
            date_str = dt.now().strftime('%Y%m%d')
            log_filename = os.path.join(log_dir, f"backtest_{date_str}.txt")
            self.backtest_log_file = open(log_filename, 'a', encoding='utf-8')
            
            # 写入分隔符和时间戳（如果是新文件）
            timestamp = dt.now().strftime('%Y-%m-%d %H:%M:%S')
            self.backtest_log_file.write(f"\n{'='*80}\n")
            self.backtest_log_file.write(f"回测开始: {timestamp}\n")
            self.backtest_log_file.write(f"{'='*80}\n\n")
            self.backtest_log_file.flush()
        except Exception as e:
            logger.warning(f"初始化回测日志文件失败: {e}")
            self.backtest_log_file = None
    
    def _write_to_backtest_log(self, text: str):
        """写入回测日志文件（同时输出到logger）"""
        logger.info(text)
        if self.backtest_log_file:
            try:
                self.backtest_log_file.write(f"{text}\n")
                self.backtest_log_file.flush()
            except:
                pass

    def load_stock_data(self, stock_list: List[str], start_date: str, end_date: str):
        """
        加载股票分钟级历史数据
        :param stock_list: 股票代码列表
        :param start_date: 起始日期 (YYYYMMDD)
        :param end_date: 结束日期 (YYYYMMDD)
        """
        logger.info(f"开始加载历史数据: {start_date} 至 {end_date}")
        logger.info(f"股票数量: {len(stock_list)}")
        logger.info(f"下载模式: {'增量下载' if self.incremental_download else '全量下载'}")
        
        # 简化逻辑：直接下载数据（增量或全量）
        # 理由：QMT官方数据是可靠的，不需要复杂的"完整性"检查
        #      - 增量下载会自动更新到最新
        #      - 全量下载会重新下载整个范围
        logger.info("正在下载/更新数据...")
        self._download_stock_data(stock_list, start_date, end_date)
        
        # 加载本地数据
        logger.info("正在加载本地数据...")
        self._load_local_stock_data(stock_list, start_date, end_date)
        
        # 验证数据范围
        self._validate_data_range(stock_list, start_date, end_date)
        
        logger.info(f"数据加载完成: 日线数据 {len(self.daily_data)} 只股票, 分钟级数据 {len(self.stock_data)} 只股票")

    def _download_stock_data(self, stock_list: List[str], start_date: str, end_date: str):
        """
        下载股票数据（日线+分钟级，根据配置选择增量或全量）
        :param stock_list: 股票代码列表
        :param start_date: 起始日期 (YYYYMMDD)
        :param end_date: 结束日期 (YYYYMMDD)
        """
        # 自动计算所需历史数据天数（考虑动态阈值和量比计算器配置）
        try:
            from config_loader import get_config_loader
            config_loader = get_config_loader()
            config = config_loader._config  # 获取原始配置字典
            
            # 获取动态阈值所需天数
            from volume_validators.dynamic_threshold import DynamicVolumeThresholdCalculator
            threshold_days = DynamicVolumeThresholdCalculator.get_required_history_days(config)
            
            # 获取量比计算器所需天数
            from volume_validators.volume_ratio_calculator import VolumeRatioCalculator
            volume_ratio_days = VolumeRatioCalculator.get_required_history_days(config)
            
            # 取两者最大值
            required_days = max(threshold_days, volume_ratio_days)
        except Exception as e:
            logger.warning(f"无法加载配置，使用默认120天: {e}")
            required_days = 120
        
        # 计算日线数据下载时间范围（自动扩展以满足动态阈值需求）
        daily_end_date = end_date
        daily_start_date = (datetime.strptime(start_date, '%Y%m%d') - timedelta(days=required_days)).strftime('%Y%m%d')
        logger.info(f"数据加载范围: {daily_start_date} ~ {daily_end_date} (自动扩展{required_days}天以满足动态阈值需求)")
        
        # 下载日线数据
        logger.info(f"下载日线数据 (模式: {'增量' if self.incremental_download else '全量'})...")
        for i, stock in enumerate(stock_list, 1):
            try:
                # 获取股票名称
                stock_name = ""
                try:
                    detail = xtdata.get_instrument_detail(stock)
                    if isinstance(detail, dict):
                        stock_name = detail.get('InstrumentName', '')
                except:
                    pass
                
                display_name = f"{stock} ({stock_name})" if stock_name else stock
                
                # 根据配置选择下载方式
                if self.incremental_download:
                    # 增量下载：让 QMT 自动从本地最后一条往后下载
                    xtdata.download_history_data(stock, '1d', '', daily_end_date, incrementally=True)
                    logger.info(f"[{i}/{len(stock_list)}] {display_name}: 增量下载日线数据")
                else:
                    # 全量下载：指定完整的起始和结束时间
                    xtdata.download_history_data(stock, '1d', daily_start_date, daily_end_date, incrementally=False)
                    logger.info(f"[{i}/{len(stock_list)}] {display_name}: 全量下载日线数据 ({daily_start_date} ~ {daily_end_date})")
                
                time.sleep(1)  # 避免请求过快
                    
            except Exception as e:
                logger.error(f"[{i}/{len(stock_list)}] {stock}: 下载日线数据异常 - {str(e)}")
        
        # 下载分钟级数据
        logger.info(f"下载分钟级数据 (模式: {'增量' if self.incremental_download else '全量'})...")
        end_time = end_date + "150000"  # 交易日结束时间 15:00:00
        
        for i, stock in enumerate(stock_list, 1):
            try:
                # 获取股票名称
                stock_name = ""
                try:
                    detail = xtdata.get_instrument_detail(stock)
                    if isinstance(detail, dict):
                        stock_name = detail.get('InstrumentName', '')
                except:
                    pass
                
                display_name = f"{stock} ({stock_name})" if stock_name else stock
                
                # 分钟数据只需要回测期间的数据（不需要延长，因为分钟数据用于逐分钟决策，不用于历史计算）
                # 注意：动态量能阈值计算使用的是日线数据，分钟数据只需回测期间的
                if self.incremental_download:
                    # 增量下载：让 QMT 自动从本地最后一条往后下载
                    xtdata.download_history_data(stock, '1m', '', end_time, incrementally=True)
                    logger.info(f"[{i}/{len(stock_list)}] {display_name}: 增量下载分钟数据")
                else:
                    # 全量下载：只下载回测期间的分钟数据（从start_date开始，不需要延长）
                    start_time_minute = start_date + "093000"  # 使用回测开始日期，不延长
                    xtdata.download_history_data(stock, '1m', start_time_minute, end_time, incrementally=False)
                    logger.info(f"[{i}/{len(stock_list)}] {display_name}: 全量下载分钟数据 ({start_date} ~ {end_date})")
                
                time.sleep(1)  # 避免请求过快
                    
            except Exception as e:
                logger.error(f"[{i}/{len(stock_list)}] {stock}: 下载分钟数据异常 - {str(e)}")
    
    def _validate_data_range(self, stock_list: List[str], start_date: str, end_date: str):
        """
        验证数据范围是否符合预期，并打印详细日志
        :param stock_list: 股票代码列表
        :param start_date: 起始日期 (YYYYMMDD)
        :param end_date: 结束日期 (YYYYMMDD)
        """
        logger.info("=" * 60)
        logger.info("📊 数据范围验证")
        logger.info("=" * 60)
        
        for stock in stock_list:
            # 检查日线数据
            if stock in self.daily_data and not self.daily_data[stock].empty:
                df = self.daily_data[stock]
                daily_start = df.index[0].strftime('%Y-%m-%d')
                daily_end = df.index[-1].strftime('%Y-%m-%d')
                logger.info(f"{stock} 日线数据: {daily_start} ~ {daily_end} ({len(df)} 条)")
            else:
                logger.warning(f"{stock} 日线数据: ❌ 无数据")
            
            # 检查分钟数据
            if stock in self.stock_data and not self.stock_data[stock].empty:
                df = self.stock_data[stock]
                minute_start = df.index[0].strftime('%Y-%m-%d')
                minute_end = df.index[-1].strftime('%Y-%m-%d')
                logger.info(f"{stock} 分钟数据: {minute_start} ~ {minute_end} ({len(df)} 条)")
                
                # 检查分钟数据是否接近结束日期
                end_date_dt = datetime.strptime(end_date, '%Y%m%d')
                last_date_dt = datetime.strptime(df.index[-1].strftime('%Y%m%d'), '%Y%m%d')
                days_diff = (end_date_dt - last_date_dt).days
                
                if days_diff > 7:
                    logger.warning(f"  ⚠️  分钟数据可能不完整！最后数据 {minute_end}，请求结束 {end_date}（相差{days_diff}天）")
                    logger.warning(f"  建议：1) 检查是否有交易日  2) 检查QMT数据源  3) 尝试设置 incremental_download: false")
            else:
                logger.warning(f"{stock} 分钟数据: ❌ 无数据")
        
        logger.info("=" * 60)


    def _load_local_stock_data(self, stock_list: List[str], start_date: str, end_date: str):
        """
        从本地加载股票数据
        :param stock_list: 股票代码列表
        :param start_date: 起始日期 (YYYYMMDD)
        :param end_date: 结束日期 (YYYYMMDD)
        """
        # 计算日线数据时间范围
        daily_end_date = end_date
        daily_start_date = (datetime.strptime(start_date, '%Y%m%d') - timedelta(days=120)).strftime('%Y%m%d')
        
        # 加载日线数据
        logger.info("正在加载本地日线数据...")
        for i, stock in enumerate(stock_list, 1):
            try:
                # 获取股票名称
                stock_name = ""
                try:
                    detail = xtdata.get_instrument_detail(stock)
                    if isinstance(detail, dict):
                        stock_name = detail.get('InstrumentName', '')
                except:
                    pass
                
                # 构建显示名称
                display_name = f"{stock} ({stock_name})" if stock_name else stock
                
                # 获取本地日线数据
                daily_data = xtdata.get_local_data(
                    stock_list=[stock],
                    period='1d',
                    start_time=daily_start_date,
                    end_time=daily_end_date,
                    dividend_type=self.dividend_type
                )
                
                if daily_data and stock in daily_data and not daily_data[stock].empty:
                    # 检查数据是否包含足够早的历史数据
                    first_date_str = str(daily_data[stock].index[0]).replace('-', '').replace('/', '')[:8]
                    if first_date_str > daily_start_date:
                        logger.warning(f"[{i}/{len(stock_list)}] {display_name}: 本地数据起始日期({first_date_str})晚于所需日期({daily_start_date})，需下载更早的数据")
                        # 下载缺失的历史数据
                        try:
                            logger.info(f"正在下载 {display_name} 的历史数据...")
                            xtdata.download_history_data(stock, '1d', daily_start_date, daily_end_date, incrementally=True)
                            time.sleep(2)  # 等待下载完成
                            # 重新加载数据
                            daily_data = xtdata.get_local_data(
                                stock_list=[stock],
                                period='1d',
                                start_time=daily_start_date,
                                end_time=daily_end_date,
                                dividend_type=self.dividend_type
                            )
                            if daily_data and stock in daily_data:
                                df = daily_data[stock]
                                df.index = pd.to_datetime(df.index)
                                self.daily_data[stock] = df
                                logger.info(f"[{i}/{len(stock_list)}] {display_name}: 重新加载后日线数据 {len(daily_data[stock])} 条")
                        except Exception as e:
                            logger.error(f"下载历史数据失败: {str(e)}")
                            df = daily_data[stock]
                            df.index = pd.to_datetime(df.index)
                            self.daily_data[stock] = df  # 使用原有数据
                    else:
                        df = daily_data[stock]
                        df.index = pd.to_datetime(df.index)
                        self.daily_data[stock] = df
                        logger.info(f"[{i}/{len(stock_list)}] {display_name}: 加载本地日线数据 {len(daily_data[stock])} 条")
                else:
                    logger.warning(f"[{i}/{len(stock_list)}] {display_name}: 无本地日线数据，尝试下载")
                    # 尝试下载数据
                    try:
                        xtdata.download_history_data(stock, '1d', daily_start_date, daily_end_date, incrementally=True)
                        time.sleep(2)
                        daily_data = xtdata.get_local_data(
                            stock_list=[stock],
                            period='1d',
                            start_time=daily_start_date,
                            end_time=daily_end_date,
                            dividend_type=self.dividend_type
                        )
                        if daily_data and stock in daily_data:
                            df = daily_data[stock]
                            df.index = pd.to_datetime(df.index)
                            self.daily_data[stock] = df
                            logger.info(f"[{i}/{len(stock_list)}] {display_name}: 下载后日线数据 {len(daily_data[stock])} 条")
                    except Exception as e:
                        logger.error(f"下载数据失败: {str(e)}")
                    
            except Exception as e:
                logger.error(f"[{i}/{len(stock_list)}] {stock}: 加载本地日线数据异常 - {str(e)}")
                logger.debug(f"详细错误信息: {repr(e)}", exc_info=True)
        
        # 加载分钟级数据
        logger.info("正在加载本地分钟级数据...")
        current_date = datetime.strptime(start_date, '%Y%m%d')
        end_date_obj = datetime.strptime(end_date, '%Y%m%d')
        
        # 创建一个字典来存储所有分钟级数据
        all_minute_data = {}
        
        while current_date <= end_date_obj:
            date_str = current_date.strftime('%Y%m%d')
            
            # 使用包含时间的格式获取分钟级数据
            start_time = date_str + "093000"  # 交易日开始时间 09:30:00
            end_time = date_str + "150000"    # 交易日结束时间 15:00:00
            
            for i, stock in enumerate(stock_list, 1):
                try:
                    # 获取股票名称
                    stock_name = ""
                    try:
                        detail = xtdata.get_instrument_detail(stock)
                        if isinstance(detail, dict):
                            stock_name = detail.get('InstrumentName', '')
                    except:
                        pass
                    
                    # 构建显示名称
                    display_name = f"{stock} ({stock_name})" if stock_name else stock
                    
                    # 获取本地分钟级数据
                    minute_data = xtdata.get_local_data(
                        stock_list=[stock],
                        period='1m',
                        start_time=start_time,
                        end_time=end_time,
                        dividend_type=self.dividend_type
                    )
                    
                    if minute_data and stock in minute_data and not minute_data[stock].empty:
                        # 合并数据
                        if stock not in all_minute_data:
                            all_minute_data[stock] = minute_data[stock]
                        else:
                            all_minute_data[stock] = pd.concat([all_minute_data[stock], minute_data[stock]]).sort_index()
                    else:
                        logger.debug(f"[{i}/{len(stock_list)}] {display_name} {date_str}: 无本地分钟级数据")
                        
                except Exception as e:
                    logger.error(f"[{i}/{len(stock_list)}] {stock} {date_str}: 加载本地分钟级数据异常 - {str(e)}")
                    logger.debug(f"详细错误信息: {repr(e)}", exc_info=True)
            
            current_date += timedelta(days=1)
        
        # 将合并后的数据存储到stock_data中
        for stock in all_minute_data:
            minute_df = all_minute_data[stock]
            
            # 检查数据质量（不进行价格转换，因为官方API应该返回统一单位）
            if stock in self.daily_data and not self.daily_data[stock].empty:
                daily_avg_price = self.daily_data[stock]['close'].mean()
                minute_avg_price = minute_df['close'].mean()
                
                # 检查价格是否异常（但不自动转换）
                if minute_avg_price > daily_avg_price * 2.0:
                    logger.warning(f"检测到 {stock} 分钟级价格异常")
                    logger.warning(f"日线平均价格: {daily_avg_price:.2f}, 分钟级平均价格: {minute_avg_price:.2f}")
                    logger.warning(f"价格比例: {minute_avg_price / daily_avg_price:.2f}")
                    logger.warning(f"请检查数据源或数据获取参数是否正确")
                else:
                    logger.info(f"{stock} 数据质量正常: 日线均价 {daily_avg_price:.2f}, 分钟级均价 {minute_avg_price:.2f}")
            
            minute_df.index = pd.to_datetime(minute_df.index)
            self.stock_data[stock] = minute_df
            logger.info(f"合并后 {stock} 的分钟级数据共 {len(self.stock_data[stock])} 条")
    
    def validate_stock_data(self, stock_list: List[str], start_date: str, end_date: str) -> List[str]:
        """
        验证股票数据的完整性，返回有效股票列表
        :param stock_list: 股票代码列表
        :param start_date: 起始日期 (YYYYMMDD)
        :param end_date: 结束日期 (YYYYMMDD)
        :return: 有效股票列表
        """
        logger.info(f"正在验证股票数据完整性: {start_date} 至 {end_date}")
        valid_stocks = []
        
        # 计算日期范围内的交易日数量
        start_date_obj = datetime.strptime(start_date, '%Y%m%d')
        end_date_obj = datetime.strptime(end_date, '%Y%m%d')
        days_count = (end_date_obj - start_date_obj).days + 1
        
        for stock in stock_list:
            try:
                # 获取股票名称
                stock_name = ""
                try:
                    detail = xtdata.get_instrument_detail(stock)
                    if isinstance(detail, dict):
                        stock_name = detail.get('InstrumentName', '')
                except:
                    pass
                
                # 构建显示名称
                display_name = f"{stock} ({stock_name})" if stock_name else stock
                
                # 检查日线数据
                if stock not in self.daily_data:
                    logger.warning(f"❌ {display_name}: 缺少日线数据")
                    logger.debug(f"当前daily_data键: {list(self.daily_data.keys())}")
                    continue
                    
                daily_df = self.daily_data[stock]
                if daily_df.empty:
                    logger.warning(f"❌ {display_name}: 日线数据为空")
                    continue
                elif len(daily_df) < 120:
                    logger.warning(f"❌ {display_name}: 日线数据不足 ({len(daily_df)}条记录 < 120)")
                    # 但仍然可以继续，只要有一些数据
                    if len(daily_df) < 10:  # 如果数据太少则跳过
                        continue
                
                # 检查分钟级数据 - 分钟级回测必须有分钟数据
                if stock not in self.stock_data:
                    logger.error(f"❌ {display_name}: 缺少分钟级数据")
                    raise ValueError(f"分钟级回测必须有分钟数据！{display_name} 缺少分钟级数据，请检查数据源")
                    
                minute_df = self.stock_data[stock]
                if minute_df.empty:
                    logger.error(f"❌ {display_name}: 无分钟级数据")
                    raise ValueError(f"分钟级回测必须有分钟数据！{display_name} 的分钟级数据为空，请检查数据源")
                
                # 检查数据完整性
                required_columns = ['open', 'high', 'low', 'close', 'volume']
                missing_daily_cols = [col for col in required_columns if col not in daily_df.columns]
                missing_minute_cols = [col for col in required_columns if col not in minute_df.columns]
                
                if missing_daily_cols:
                    logger.warning(f"❌ {display_name}: 日线数据缺少必要字段 {missing_daily_cols}")
                    continue
                    
                if missing_minute_cols:
                    logger.warning(f"❌ {display_name}: 分钟级数据缺少必要字段 {missing_minute_cols}")
                    continue
                
                # 检查是否有有效价格数据
                daily_close_valid = (daily_df['close'] > 0).any() if 'close' in daily_df.columns else False
                daily_volume_valid = (daily_df['volume'] > 0).any() if 'volume' in daily_df.columns else False
                minute_close_valid = (minute_df['close'] > 0).any() if 'close' in minute_df.columns else False
                minute_volume_valid = (minute_df['volume'] > 0).any() if 'volume' in minute_df.columns else False
                
                if not (daily_close_valid and daily_volume_valid and minute_close_valid and minute_volume_valid):
                    logger.warning(f"❌ {display_name}: 价格或成交量数据异常 "
                                 f"(日线close>0: {daily_close_valid}, 日线volume>0: {daily_volume_valid}, "
                                 f"分钟close>0: {minute_close_valid}, 分钟volume>0: {minute_volume_valid})")
                    continue
                
                valid_stocks.append(stock)
                logger.info(f"✅ {display_name}: 数据有效")
                    
            except Exception as e:
                logger.warning(f"❌ {stock}: 数据验证异常 - {str(e)}")
                logger.debug(f"详细错误信息: {repr(e)}", exc_info=True)
        
        logger.info(f"数据验证完成：{len(valid_stocks)}/{len(stock_list)} 只股票数据有效")
        return valid_stocks

    def load_strategy_module(self, strategy_file: str):
        """
        动态加载策略模块
        :param strategy_file: 策略文件路径（不含.py后缀）
        """
        try:
            import importlib.util
            import sys
            
            # 构建完整文件路径
            if not strategy_file.endswith('.py'):
                strategy_file += '.py'
            
            # 支持strategies文件夹和根目录
            # 从backtest_systems文件夹回到项目根目录
            project_root = os.path.dirname(os.path.dirname(__file__))
            strategies_dir = os.path.join(project_root, 'strategies')
            root_dir = project_root
            
            # 优先查找strategies文件夹，再查找根目录
            if os.path.exists(os.path.join(strategies_dir, strategy_file)):
                file_path = os.path.join(strategies_dir, strategy_file)
            else:
                file_path = os.path.join(root_dir, strategy_file)
            
            if not os.path.exists(file_path):
                raise FileNotFoundError(f"策略文件不存在: {file_path}")
            
            # 动态导入模块
            spec = importlib.util.spec_from_file_location("strategy_module", file_path)
            if spec is None:
                raise ValueError(f"无法从文件创建模块规范: {file_path}")
                
            strategy_module = importlib.util.module_from_spec(spec)
            if spec.loader is not None:
                spec.loader.exec_module(strategy_module)
            else:
                raise ValueError(f"无法加载模块: {file_path}")
            
            # 查找策略类
            strategy_class = None
            for name in dir(strategy_module):
                obj = getattr(strategy_module, name)
                if (isinstance(obj, type) and 
                    name.endswith('Strategy') and 
                    hasattr(obj, 'check_conditions')):
                    strategy_class = obj
                    break
            
            if strategy_class is None:
                raise ValueError(f"在{strategy_file}中未找到策略类")
            
            self.strategy_class = strategy_class
            self.strategy_module = strategy_module
            logger.info(f"成功加载策略: {strategy_class.__name__} from {strategy_file}")
            
        except Exception as e:
            logger.error(f"加载策略模块失败: {str(e)}")
            raise

    def create_strategy_instance_for_backtest(self):
        """
        为回测创建策略实例（不启动实时监控）
        """
        if not hasattr(self, 'strategy_class'):
            raise ValueError("请先加载策略模块")
        
        # 创建内联的策略包装器
        class MinuteStrategyWrapper:
            def __init__(self, original_strategy_class, backtest_config):
                self.original_class = original_strategy_class
                self.backtest_config = backtest_config  # 保存回测配置的引用
                
                # ============ 使用类方法工厂创建策略实例 ============
                # 使用 for_backtest() 类方法创建实例，确保正确初始化
                self.strategy_instance = original_strategy_class.for_backtest()
                
                # ============ 重置部分属性以适应回测环境 ============
                # 清空数据结构（回测时会重新填充）
                self.strategy_instance.stock_pool = []
                self.strategy_instance.hist_np_cache = {}
                self.strategy_instance.today_data = {}
                self.strategy_instance.float_shares = {}
                self.strategy_instance.float_shares_source = {}
                self.strategy_instance.buy_stocks = set()
                self.strategy_instance.stock_strategy_types = {}
                self.strategy_instance.daily_data_cache = {}
                self.strategy_instance.sell_stocks = set()
                
                # 从配置文件加载所有策略参数
                from config_loader import (
                    STRATEGY_CONFIG,
                    BUY_PARAMS, 
                    SELL_PARAMS, 
                    MARKET_CAP_THRESHOLD
                )
                
                self.strategy_instance.strategy_config = STRATEGY_CONFIG
                self.strategy_instance.last_strategy_type = None
                self.strategy_instance.buy_params = BUY_PARAMS
                self.strategy_instance.sell_params = SELL_PARAMS
                self.strategy_instance.market_cap_threshold = MARKET_CAP_THRESHOLD
                
                # 一次性绑定所有策略方法
                self._bind_strategy_methods()
            
            def _bind_strategy_methods(self):
                """一次性绑定所有策略方法到单个实例"""
                import functools
                
                # 需要绑定的方法列表（包括双轨策略方法、市值方法和仓位管理方法）
                method_names = [
                    'analyze_volume_price_relationship',
                    'detect_volume_price_divergence',
                    'get_volume_ratio',
                    'check_conditions',
                    'check_sell_conditions',
                    '_calculate_price_position',
                    '_check_normal_strategy',
                    '_check_strong_stock_strategy',
                    '_calculate_ma_spacing',
                    '_calculate_volume_position',
                    '_check_volatility',
                    '_check_relative_strength',
                    '_calculate_volume_percentile',
                    '_check_continuous_shrinking_volume',
                    '_calculate_adaptive_threshold',
                    'get_outstanding_shares',
                    'get_stock_market_cap',
                    'get_stock_type',
                    '_extract_volume_strength',
                    'log_buy_signal',
                    'log_sell_signal',
                    'print_stock_info'
                ]
                
                for method_name in method_names:
                    if hasattr(self.original_class, method_name):
                        original_method = getattr(self.original_class, method_name)
                        bound_method = functools.partial(original_method, self.strategy_instance)
                        setattr(self.strategy_instance, method_name, bound_method)
                
                # 初始化仓位管理模块
                try:
                    from position_management import (
                        PositionManager,
                        PositionCalculator,
                        TradeProtector
                    )
                    from config_loader import (
                        POSITION_CALCULATOR_CONFIG,
                        TRADE_PROTECTOR_CONFIG,
                        BACKTEST_CONFIG,
                        MARKET_CAP_THRESHOLD
                    )
                    from utils.slippage_calculator import SlippageCalculator
                    
                    # 创建仓位管理器（回测模式）
                    self.strategy_instance.position_manager = PositionManager(mode='backtest')
                    
                    # 创建仓位计算器（使用配置文件）
                    self.strategy_instance.position_calculator = PositionCalculator(POSITION_CALCULATOR_CONFIG)
                    
                    # 创建交易保护器（使用配置文件）
                    self.strategy_instance.trade_protector = TradeProtector(TRADE_PROTECTOR_CONFIG)
                    
                    logger.info("✅ 仓位管理模块初始化完成")
                except ImportError as e:
                    logger.warning(f"⚠️ 无法导入模块: {e}")
                
                # 初始化量比计算器
                try:
                    from volume_validators.volume_ratio_calculator import VolumeRatioCalculator
                    from config_loader import get_config_loader
                    config_loader = get_config_loader()
                    config = config_loader._config
                    self.volume_ratio_calculator = VolumeRatioCalculator(config)
                    logger.info("✅ 量比计算器初始化完成")
                except Exception as e:
                    logger.warning(f"⚠️ 无法初始化量比计算器: {e}")
                    self.volume_ratio_calculator = None
                
            def prepare_stock_data(self, stock, daily_data, minute_data, current_minute_idx):
                """为单只股票准备数据"""
                # 将日线数据转换为策略需要的格式
                self.strategy_instance.daily_data_cache[stock] = daily_data
                # 安全地处理时间索引，确保能正确转换为字符串格式
                time_values = []
                for d in daily_data.index:
                    time_values.append(str(d).replace('-', '').replace('/', '')[:8])
                
                # 存储历史数据，用于计算均线
                # 注意：这里存储的是到前一日为止的数据，不包含当前交易日
                # 过滤掉当前交易日的数据，只保留历史数据
                # 简化日期获取逻辑，直接使用字符串处理
                try:
                    if len(minute_data) > 0:
                        # 直接从分钟数据索引获取日期字符串
                        current_date_str = str(minute_data.index[0]).replace('-', '').replace('/', '')[:8]
                    else:
                        # 如果没有分钟数据，使用日线数据的最后一天
                        current_date_str = str(daily_data.index[-1]).replace('-', '').replace('/', '')[:8]
                except Exception as e:
                    logger.warning(f"获取当前日期失败: {e}，使用默认日期")
                    # 如果都失败了，使用一个默认日期
                    current_date_str = "20250101"
                
                # 安全地过滤历史数据
                try:
                    # 使用pandas的字符串方法
                    daily_date_str = daily_data.index.astype(str).str.replace('-', '').str[:8]
                    historical_daily_data = daily_data[daily_date_str < current_date_str]
                except Exception as e:
                    logger.warning(f"使用pandas方法过滤历史数据失败: {e}，尝试手动过滤")
                    # 手动过滤
                    filtered_mask = []
                    for idx in daily_data.index:
                        try:
                            idx_date = str(idx).replace('-', '').replace('/', '')[:8]
                            filtered_mask.append(idx_date < current_date_str)
                        except:
                            filtered_mask.append(False)
                    historical_daily_data = daily_data[filtered_mask]
                
                if len(historical_daily_data) < 20:  # 至少需要20天历史数据
                    # 输出详细调试信息
                    if len(daily_data) > 0:
                        logger.warning(f"{stock} 历史数据不足20天，跳过")
                        logger.warning(f"  原始日线数据: {len(daily_data)}天, 日期范围: {daily_data.index[0]} 到 {daily_data.index[-1]}")
                        logger.warning(f"  当前交易日: {current_date_str}")
                        logger.warning(f"  过滤后历史数据: {len(historical_daily_data)}天")
                        logger.warning(f"  问题：所有日线数据的日期都 >= 当前交易日，无历史数据可用于计算均线")
                    else:
                        logger.warning(f"{stock} 历史数据不足20天，跳过 [原始数据:{len(daily_data)}天]")
                    return
                
                # 为历史数据准备时间格式
                hist_time_values = []
                for d in historical_daily_data.index:
                    hist_time_values.append(str(d).replace('-', '').replace('/', '')[:8])
                
                # hist_np_cache应该只包含历史数据（不含当日），这样策略计算均线时才正确
                # 策略会从hist_np_cache取历史数据，从today_data取当日数据
                # 包含完整OHLC数据用于技术形态分析（如上影线识别）
                self.strategy_instance.hist_np_cache[stock] = {
                    'close': historical_daily_data['close'].values,
                    'open': historical_daily_data['open'].values,
                    'high': historical_daily_data['high'].values,
                    'low': historical_daily_data['low'].values,
                    'volume': historical_daily_data['volume'].values,
                    'time': hist_time_values
                }
                
                # 构建实时K线数据（模拟实盘中每分钟更新的K线形态）
                if current_minute_idx >= 0 and current_minute_idx < len(minute_data):
                    # 获取到当前分钟为止的数据
                    current_slice = minute_data.iloc[:current_minute_idx+1]
                    
                    # 计算实际交易分钟数（从时间戳计算）
                    # A股交易时间：9:30-11:30 (120分钟)，13:00-15:00 (120分钟)
                    current_time = current_slice.index[-1]
                    hour = current_time.hour
                    minute = current_time.minute
                    
                    if hour < 11 or (hour == 11 and minute <= 30):
                        # 上午盘：9:30-11:30
                        actual_trading_minutes = (hour - 9) * 60 + minute - 30
                    elif hour >= 13:
                        # 下午盘：13:00-15:00
                        # 上午120分钟 + 下午已交易的分钟数
                        actual_trading_minutes = 120 + (hour - 13) * 60 + minute
                    else:
                        # 中午休市时间（11:30-13:00），使用上午的120分钟
                        actual_trading_minutes = 120
                    
                    # 确保至少为1分钟
                    actual_trading_minutes = max(1, actual_trading_minutes)
                    
                    # 计算量比（使用固定窗口实时量比/累计量比）
                    current_volume = int(current_slice['volume'].sum())
                    time_tag = current_time.strftime('%Y-%m-%d %H:%M:%S')
                    
                    # 使用累计量比计算
                    volume_ratio = self.volume_ratio_calculator.calculate_cumulative_volume_ratio(
                        stock,
                        current_volume=current_volume,
                        historical_daily_data=historical_daily_data,
                        actual_trading_minutes=actual_trading_minutes,
                        time_tag=time_tag
                    )
                    
                    # 构建实时K线数据
                    # 使用当前分钟的价格作为当日收盘价（在回测中，当前分钟价格就是当日收盘价）
                    current_price = float(current_slice['close'].iloc[-1])  # 当前分钟价格作为当日收盘价
                    
                    # 获取当前分钟的时间戳
                    current_time = pd.to_datetime(current_slice.index[-1])
                    
                    # 计算涨幅（相对于昨日收盘价）
                    # hist_np_cache 只包含历史数据（不含当日），所以 [-1] 就是昨日收盘
                    last_close = float(self.strategy_instance.hist_np_cache[stock]['close'][-1] if len(self.strategy_instance.hist_np_cache[stock]['close']) > 0 else current_slice['close'].iloc[-1])
                    change_pct = (current_price - last_close) / last_close if last_close > 0 else 0.0
                    
                    self.strategy_instance.today_data[stock] = {
                        'open': float(current_slice['open'].iloc[0]),  # 开盘价
                        'high': float(current_slice['high'].max()),    # 最高价
                        'low': float(current_slice['low'].min()),      # 最低价
                        'last': current_price, # 当前分钟价格作为当日收盘价
                        'volume': current_volume, # 当日累计成交量（从开盘到当前分钟）
                        'lastClose': last_close,  # 昨日收盘价
                        'changePct': change_pct,  # 涨幅（小数形式，如0.05表示5%）
                        'volRatio': volume_ratio,  # 添加量比数据
                        'time': current_time  # ✅ 添加时间字段（分钟级回测使用时间戳）
                    }
                else:
                    # 分钟级回测必须有分钟数据，没有就报错
                    raise ValueError(f"{stock} 缺少分钟级数据，无法进行分钟级回测")
                
                # 设置默认流通股本（回测中可简化）
                self.strategy_instance.float_shares[stock] = self.backtest_config['default_float_shares']
                
            def check_sell_conditions(self, stock):
                """调用原策略的卖出条件检查方法（使用单例实例）"""
                try:
                    # 直接调用单例实例的方法，无需创建临时实例
                    if hasattr(self.strategy_instance, 'check_sell_conditions'):
                        result = self.strategy_instance.check_sell_conditions(stock)
                        return result
                    else:
                        logger.error(f"  {stock}: 策略无卖出逻辑")
                        return {'should_sell': False, 'reason': '策略无卖出逻辑'}
                    
                except Exception as e:
                    logger.error(f"调用策略卖出条件检查时出错: {str(e)}")
                    return {'should_sell': False, 'reason': f'卖出检查异常: {str(e)}'}
            
            def check_conditions(self, stock):
                """调用原策略的条件检查方法（使用单例实例）"""
                try:
                    # 直接调用单例实例的方法，无需创建临时实例
                    return self.strategy_instance.check_conditions(stock)
                    
                except Exception as e:
                    logger.error(f"调用策略条件检查时出错: {str(e)}")
                    logger.debug(f"详细错误信息: {repr(e)}", exc_info=True)
                    return False
            
        
        self.strategy_wrapper = MinuteStrategyWrapper(self.strategy_class, self.backtest_config)
        logger.info("策略实例创建完成")
        
        # 初始化技术形态引擎（在策略实例创建后）
        self._initialize_technical_engine()
    
    def _load_tech_enhancement_config(self, technical_config: Dict):
        """
        加载技术形态增强配置
        
        如果配置缺失，记录警告并禁用相关功能，不允许硬编码默认值
        """
        try:
            signal_enhancement = technical_config.get('signal_enhancement')
            if signal_enhancement is None:
                logger.warning("⚠️ 技术形态增强配置缺失（signal_enhancement），将禁用技术形态增强功能")
                self.tech_enhancement_config = None
                return
            
            validation_impact = signal_enhancement.get('validation_impact')
            if validation_impact is None:
                logger.error("❌ 技术形态验证影响配置缺失（validation_impact），无法使用技术形态增强功能")
                raise ValueError("技术形态增强配置不完整：缺少 validation_impact 配置")
            
            self.tech_enhancement_config = {
                'bypass_threshold': validation_impact.get('bypass_threshold'),
                'relax_threshold': validation_impact.get('relax_threshold')
            }
            
            # 验证配置完整性
            if self.tech_enhancement_config['bypass_threshold'] is None:
                raise ValueError("技术形态增强配置不完整：缺少 bypass_threshold")
            if self.tech_enhancement_config['relax_threshold'] is None:
                raise ValueError("技术形态增强配置不完整：缺少 relax_threshold")
            
            logger.info(f"✅ 技术形态增强配置已加载: 绕过阈值={self.tech_enhancement_config['bypass_threshold']:.0%}, "
                       f"放宽阈值={self.tech_enhancement_config['relax_threshold']:.0%}")
            
        except Exception as e:
            logger.error(f"❌ 技术形态增强配置加载失败: {e}")
            logger.error("⚠️ 将禁用技术形态增强功能，避免使用错误的硬编码参数导致交易亏损")
            self.tech_enhancement_config = None
    
    def _initialize_technical_engine(self):
        """初始化技术形态引擎"""
        try:
            from config_loader import load_technical_config
            from technical_engine import TechnicalEngine
            technical_config = load_technical_config()
            
            # 输出配置信息
            logger.info("=" * 60)
            logger.info("🎯 技术形态引擎配置")
            enabled_patterns = []
            for pattern_name in ['upper_shadow', 'doji', 'hammer']:
                if technical_config.get(pattern_name, {}).get('enabled', False):
                    enabled_patterns.append(pattern_name)
            
            if enabled_patterns:
                logger.info(f"  启用形态: {', '.join(enabled_patterns)}")
            else:
                logger.info("  ⚠️ 所有形态均未启用")
            
            self.technical_engine = TechnicalEngine(technical_config, strategy_instance=None)
            self.technical_enabled = True
            
            # 将技术引擎传递给策略实例（用于融合模式） 双向绑定
            if hasattr(self, 'strategy_wrapper') and hasattr(self.strategy_wrapper, 'strategy_instance'):
                strategy_inst = self.strategy_wrapper.strategy_instance
                strategy_inst.technical_engine = self.technical_engine
                self.technical_engine.strategy = strategy_inst  # 新增：反向传递策略实例
                
                logger.info("✅ 技术引擎已传递给策略实例（融合模式+双向绑定）")
            
            logger.info("✅ 技术形态引擎初始化完成")
            logger.info("=" * 60)
        except Exception as e:
            logger.warning(f"⚠️ 技术形态引擎初始化失败，将禁用技术信号: {e}")
            logger.exception(e)  # 输出完整错误堆栈
            self.technical_engine = None
            self.technical_enabled = False

    def run_backtest(self, strategy_file: str, stock_list: List[str], date: str, end_date: str = None) -> Dict:
        """
        运行回测
        :param strategy_file: 策略文件名
        :param stock_list: 股票列表
        :param date: 回测日期 (YYYYMMDD)
        :return: 回测结果
        """
        try:
            logger.info(f"开始运行回测: 策略={strategy_file}, 日期={date}, 股票数={len(stock_list)}")
            
            # 加载策略模块
            self.load_strategy_module(strategy_file)
            
            
            # 设置回测时间范围
            start_date = date
            if end_date is None:
                # 使用系统当前日期作为结束日期
                end_date = datetime.now().strftime('%Y%m%d')
                logger.info(f"未指定结束日期，使用系统当前日期: {end_date}")
            
            # 加载股票数据
            self.load_stock_data(stock_list, start_date, end_date)
            
            # 验证数据完整性
            valid_stocks = self.validate_stock_data(stock_list, start_date, end_date)
            if not valid_stocks:
                raise ValueError("没有有效的股票数据用于回测")
            
            logger.info(f"有效股票: {valid_stocks}")
            
            # 创建策略实例
            self.create_strategy_instance_for_backtest()
            
            # 初始化回测结果
            results = {
                'date': date,
                'strategy': strategy_file,
                'initial_capital': self.initial_capital,
                'final_capital': self.initial_capital,
                'total_return': 0.0,
                'annual_return': 0.0,
                'max_drawdown': 0.0,
                'sharpe_ratio': 0.0,
                'win_rate': 0.0,
                'trades': [],
                'positions': {}
            }
            
            # 重置交易记录和持仓
            self.trades = []
            self.positions = {}
            self.current_capital = self.initial_capital
            self.daily_capital_usage = []  # 重置每日资金占用记录
            
            # 按日期和分钟进行回测
            logger.info("开始逐分钟回测...")
            
            # 直接使用回测开始日期，不需要预热期（数据已延长，有足够历史数据）
            actual_start_date = datetime.strptime(start_date, '%Y%m%d')
            warmup_start_date = actual_start_date
            warmup_days = 0
            logger.info(f"数据已延长，直接从回测开始日期开始: {start_date}")
            
            # 初始化当前日期和结束日期对象
            current_date = datetime.strptime(start_date, '%Y%m%d')
            end_date_obj = datetime.strptime(end_date, '%Y%m%d')
            
            # 使用官方API获取回测日期范围内的所有交易日
            try:
                # 先下载交易日历数据
                xtdata.download_holiday_data()
                trading_dates_list = xtdata.get_trading_calendar('SH', start_date, end_date)
                if trading_dates_list:
                    # 转换时间戳为日期字符串列表
                    trading_dates_str = []
                    for ts in trading_dates_list:
                        if isinstance(ts, (int, float)):
                            date_obj = datetime.fromtimestamp(ts / 1000)
                            trading_dates_str.append(date_obj.strftime('%Y%m%d'))
                        else:
                            trading_dates_str.append(str(ts))
                    total_trading_days = len(trading_dates_str)
                    logger.info(f"使用官方API获取交易日历: 共{total_trading_days}个交易日")
                else:
                    # 如果API失败，使用备用方法
                    total_trading_days = 0
                    temp_date = current_date
                    while temp_date <= end_date_obj:
                        if temp_date.weekday() < 5:
                            total_trading_days += 1
                        temp_date += timedelta(days=1)
                    logger.warning("使用备用方法计算交易日数")
            except Exception as e:
                # 如果API失败，使用备用方法
                total_trading_days = 0
                temp_date = current_date
                while temp_date <= end_date_obj:
                    if temp_date.weekday() < 5:
                        total_trading_days += 1
                    temp_date += timedelta(days=1)
                logger.warning(f"获取交易日历失败，使用备用方法: {e}")
            
            processed_days = 0
            last_progress = -1
            
            # 格式化日期显示
            start_date_formatted = f"{start_date[:4]}-{start_date[4:6]}-{start_date[6:8]}"
            end_date_formatted = f"{end_date[:4]}-{end_date[4:6]}-{end_date[6:8]}"
            
            logger.info(f"回测开始日期: {start_date_formatted}")
            logger.info(f"回测结束日期: {end_date_formatted}")
            
            while current_date <= end_date_obj:
                date_str = current_date.strftime('%Y%m%d')
                
                # 检查是否为交易日（周一到周五）
                if current_date.weekday() < 5:
                    processed_days += 1
                    progress = int((processed_days / total_trading_days) * 100)
                    
                    # 使用\r动态更新进度显示，避免覆盖交易信号输出
                    # 格式化日期显示
                    formatted_date = f"{date_str[:4]}-{date_str[4:6]}-{date_str[6:8]}"
                    print(f"\r回测进度: {progress}% ({processed_days}/{total_trading_days}) - 当前日期: {formatted_date}", end='', flush=True)
                    last_progress = progress
                    
                    # 清空策略的去重集合（每天重新开始）
                    if self.strategy_wrapper is not None and hasattr(self.strategy_wrapper, 'strategy_instance'):
                        strategy = self.strategy_wrapper.strategy_instance
                        strategy.buy_stocks.clear()
                        strategy.sell_stocks.clear()
                        strategy.logged_sell_signals.clear()
                        
                        # 清空技术引擎的日志去重集合（每天重新开始）
                        if hasattr(strategy, 'technical_engine') and strategy.technical_engine:
                            strategy.technical_engine.logged_independent.clear()
                            strategy.technical_engine.logged_enhanced.clear()
                            strategy.technical_engine.logged_strong_confirmation.clear()
                    
                    # 清空日志去重集合（每天重新开始）
                    # 只保留当前日期的记录，删除其他日期的记录
                    current_date_key = date_str
                    keys_to_remove = [k for k in self.logged_filtered_signals.keys() if k[0] != current_date_key]
                    for k in keys_to_remove:
                        del self.logged_filtered_signals[k]
                    self.logged_t1_intercepts = {k for k in self.logged_t1_intercepts if k[0] == current_date_key}
                    keys_to_remove = [k for k in self.logged_confirmed_signals.keys() if k[0] != current_date_key]
                    for k in keys_to_remove:
                        del self.logged_confirmed_signals[k]
                    
                    # 清空策略的每日去重集合（包括技术形态诊断记录和动态阈值去重）
                    if self.strategy_wrapper is not None and hasattr(self.strategy_wrapper, 'strategy_instance'):
                        strategy = self.strategy_wrapper.strategy_instance
                        strategy.buy_stocks.clear()
                        strategy.sell_stocks.clear()
                        strategy.logged_sell_signals.clear()
                        # 清空动态阈值计算日志去重集合（每天重新开始，与logged_sell_signals一致）
                        strategy.logged_threshold_calculations.clear()
                        
                        # 清空技术引擎的日志去重集合（每天重新开始）
                        if hasattr(strategy, 'technical_engine') and strategy.technical_engine:
                            strategy.technical_engine.logged_independent.clear()
                            strategy.technical_engine.logged_enhanced.clear()
                            strategy.technical_engine.logged_strong_confirmation.clear()
                        # 清空技术形态诊断记录
                        if hasattr(strategy, '_logged_tech_diagnosis'):
                            strategy._logged_tech_diagnosis.clear()
                    
                    # ===== 每天开始时：同步持仓，确保PositionInfo对象存在 =====
                    # 为每个持仓创建或获取PositionInfo对象（用于止损管理）
                    for stock in list(self.positions.keys()):
                        if stock in self.position_batches:
                            if 'position_info' not in self.position_batches[stock]:
                                # 创建PositionInfo对象
                                batches = self.position_batches[stock].get('batches', [])
                                if batches:
                                    first_batch = batches[0]
                                    buy_time_str = first_batch.get('time', '')
                                    buy_time = datetime.strptime(buy_time_str, '%Y-%m-%d %H:%M:%S') if buy_time_str else datetime.now()
                                    buy_price = first_batch.get('price', 0)
                                    total_shares = sum(b['shares'] for b in batches)
                                    
                                    position_info = PositionInfo(
                                        stock=stock,
                                        buy_time=buy_time,
                                        buy_price=buy_price,
                                        shares=total_shares
                                    )
                                    # 初始化止损
                                    stock_data = self.daily_data.get(stock, pd.DataFrame())
                                    position_info.initialize_stop_loss(stock_data if not stock_data.empty else None)
                                    
                                    self.position_batches[stock]['position_info'] = position_info
                                    logger.debug(f"同步持仓并初始化止损: {stock} @ {buy_price:.2f}")
                    
                    # 为每个有效股票进行回测
                    for stock in valid_stocks:
                        try:
                            # 获取该股票的日线和分钟级数据
                            daily_data = self.daily_data.get(stock)
                            minute_data = self.stock_data.get(stock)
                            
                            if daily_data is None or minute_data is None:
                                continue
                                
                            # 筛选当天的分钟数据（使用日期范围过滤，性能更好）
                            day_start = current_date.replace(hour=0, minute=0, second=0, microsecond=0)
                            day_end = current_date.replace(hour=23, minute=59, second=59, microsecond=999999)
                            current_day_minute_data = minute_data[(minute_data.index >= day_start) & (minute_data.index <= day_end)]
                            
                            if current_day_minute_data.empty:
                                continue
                                
                            # 为策略实例准备数据
                            # 确保strategy_wrapper不为None
                            buy_signals = 0
                            sell_signals = 0
                            
                            if self.strategy_wrapper is not None:
                                # 逐分钟检查策略条件
                                for minute_idx in range(len(current_day_minute_data)):
                                    # 更新策略数据
                                    self.strategy_wrapper.prepare_stock_data(stock, daily_data, current_day_minute_data, minute_idx)
                                    
                                    
                                    # ===== 每分钟开始：更新所有持仓的止损状态 =====
                                    # 在检查任何交易信号之前，先更新所有持仓的最高价和ATR
                                    if stock in self.positions and stock in self.position_batches:
                                        position_info = self.position_batches[stock].get('position_info')
                                        if position_info:
                                            try:
                                                current_price = current_day_minute_data.iloc[minute_idx]['close']
                                                stock_data = self.daily_data.get(stock, pd.DataFrame())
                                                position_info.update_stop_loss(
                                                    current_price=current_price,
                                                    stock_data=stock_data if not stock_data.empty else None
                                                )
                                            except Exception as e:
                                                logger.debug(f"更新止损状态失败 {stock}: {e}")
                                    
                                    # 检查买入条件（仅在未持仓且当天未买入过且当天未卖出过时）
                                    if (hasattr(self.strategy_wrapper, 'strategy_instance') and
                                        stock not in self.positions and 
                                        stock not in self.strategy_wrapper.strategy_instance.buy_stocks and 
                                        stock not in self.strategy_wrapper.strategy_instance.sell_stocks):
                                        # 检查是否满足买入条件
                                        condition_result = self.strategy_wrapper.check_conditions(stock)
                                                                    
                                        if condition_result:
                                            # 输出策略条件满足信息（包含策略类型和关键参数）
                                            try:
                                                stock_today_data = self.strategy_wrapper.strategy_instance.today_data.get(stock, {})
                                                volume_ratio = stock_today_data.get('volRatio', 0)
                                                price_change_pct = stock_today_data.get('changePct', 0) * 100
                                                strategy_type = self.strategy_wrapper.strategy_instance.last_strategy_type or "未知策略"
                                                
                                                # 构建策略条件满足的输出信息
                                                strategy_info = f"策略{strategy_type}条件满足: 量比{volume_ratio:.2f}"
                                                if price_change_pct > 0:
                                                    strategy_info += f" | 涨幅+{price_change_pct:.2f}%"
                                                else:
                                                    strategy_info += f" | 涨幅{price_change_pct:.2f}%"
                                                
                                                logger.info(f"✅ {stock} {strategy_info}")
                                            except Exception as e:
                                                # 如果获取参数失败，使用简化输出
                                                logger.info(f"✅ {stock} 策略条件满足")
                                            
                                            # 执行买入操作
                                            try:
                                                self._execute_buy(stock, current_day_minute_data.iloc[minute_idx])
                                                # 标记当天已买入
                                                self.strategy_wrapper.strategy_instance.buy_stocks.add(stock)
                                            except Exception as e:
                                                logger.error(f"调用_execute_buy函数时出现异常: {str(e)}")
                                            buy_signals += 1
                                            # 买入后，跳过本分钟的卖出检查，避免同一分钟买入后立即卖出
                                            continue
                                    
                                    # 检查加仓条件（基于配置文件的批次设置）
                                    if (hasattr(self.strategy_wrapper, 'strategy_instance') and
                                        stock in self.positions and 
                                        stock in self.position_batches and 
                                        stock not in self.strategy_wrapper.strategy_instance.buy_stocks):
                                        # 从配置读取批次信息
                                        batch_config = self._get_batch_config()
                                        current_batch = self.position_batches[stock].get('current_batch', 1)
                                        total_batches = batch_config['total_batches']
                                        
                                        # 检查是否还有下一批可以加仓（动态判断，不硬编码）
                                        if batch_config['enabled'] and current_batch < total_batches:
                                            # 检查是否满足加仓条件
                                            should_add, reason = self._check_add_position_conditions(stock, current_day_minute_data.iloc[minute_idx])
                                            
                                            if should_add:
                                                try:
                                                    # 临时清理mock_positions，让策略输出完整的买入日志
                                                    temp_removed = False
                                                    if (hasattr(self.strategy_wrapper, 'strategy_instance') and 
                                                        hasattr(self.strategy_wrapper.strategy_instance, 'mock_positions') and
                                                        stock in self.strategy_wrapper.strategy_instance.mock_positions):
                                                        del self.strategy_wrapper.strategy_instance.mock_positions[stock]
                                                        temp_removed = True
                                                        logger.debug(f"临时清理mock_positions以输出加仓日志: {stock}")
                                                    
                                                    # 执行下一批加仓（会触发完整的买入日志）
                                                    next_batch = current_batch + 1
                                                    self._execute_add_position(stock, current_day_minute_data.iloc[minute_idx])
                                                    # 标记当天已加仓（视为已买入）
                                                    self.strategy_wrapper.strategy_instance.buy_stocks.add(stock)
                                                    buy_signals += 1
                                                    logger.info(f"✅ 第{next_batch}批加仓完成: {stock} - {reason}")
                                                except Exception as e:
                                                    logger.error(f"加仓{stock}时出现异常: {str(e)}")
                                    
                                    # ===== 优先级1：独立止损检查（使用止损服务）=====
                                    # 准备止损服务需要的数据
                                    positions_for_check = {}
                                    current_prices = {}
                                    stock_data_dict = {}
                                    
                                    for stock in self.positions.keys():
                                        if stock in self.position_batches:
                                            position_info_obj = self.position_batches[stock].get('position_info')
                                            if position_info_obj:
                                                positions_for_check[stock] = position_info_obj
                                                # 获取当前价格
                                                try:
                                                    current_prices[stock] = current_day_minute_data.iloc[minute_idx]['close']
                                                    # 提供历史数据用于ATR更新
                                                    stock_data_dict[stock] = self.daily_data.get(stock)
                                                except:
                                                    pass
                                    
                                    # 调用止损服务进行批量检查
                                    if positions_for_check:
                                        stop_loss_list = self.stop_loss_service.check_all_positions(
                                            positions=positions_for_check,
                                            current_prices=current_prices,
                                            current_date=date_str,
                                            stock_data_dict=stock_data_dict
                                        )
                                        
                                        # 执行止损
                                        for stock, reason in stop_loss_list:
                                            try:
                                                # 立即全部清仓（不分批）
                                                self._execute_sell(
                                                    stock, 
                                                    current_day_minute_data.iloc[minute_idx], 
                                                    reason, 
                                                    sell_ratio=1.0,  # 全部清仓
                                                    sell_result=None  # 止损卖出，无技术形态信息
                                                )
                                                sell_signals += 1
                                                
                                                # 标记已处理（避免重复止损）
                                                self.strategy_wrapper.strategy_instance.sell_stocks.add(stock)
                                            except Exception as e:
                                                logger.error(f"{stock} 执行止损时异常: {str(e)}")
                                    
                                    # ===== 优先级2：检查策略卖出条件 + 技术形态信号 =====
                                    # 检查卖出条件（仅在已持仓且当天未处理过卖出信号时）
                                    # 关键：只有在sell_stocks=False时才会进入这个if块
                                    if (self.strategy_wrapper is not None and 
                                        hasattr(self.strategy_wrapper, 'strategy_instance') and
                                        stock in self.positions and 
                                        stock not in self.strategy_wrapper.strategy_instance.sell_stocks):
                                        # 检查策略卖出条件（技术形态已融合到策略内部，不再独立检查）
                                        try:
                                            sell_result = self.strategy_wrapper.check_sell_conditions(stock)
                                        except Exception as e:
                                            logger.error(f"{stock}: 卖出条件检查异常: {str(e)}")
                                            sell_result = {'should_sell': False, 'reason': f'检查异常: {str(e)}'}
                                        
                                        # 关键：在外部定义current_minute_time，确保在后续日志中可用
                                        current_minute_time = current_day_minute_data.index[minute_idx]
                                        
                                        # 基础卖出条件满足后，使用增强验证系统进行二次确认
                                        if sell_result.get('should_sell', False):
                                            # 检查是否绕过验证（条件D：技术形态独立触发）
                                            bypass_validation = sell_result.get('bypass_validation', False)
                                            
                                            # 获取持仓信息（用于止损计算）
                                            position_info = self.position_batches.get(stock, {})
                                            batches = position_info.get('batches', [])
                                            avg_price = sum(b['price'] * b['shares'] for b in batches) / sum(b['shares'] for b in batches) if batches else 0
                                            position_data = {
                                                'avg_price': avg_price,
                                                'total_shares': self.positions.get(stock, 0),
                                                'batches': batches
                                            }
                                            
                                            # 条件D（技术形态独立触发）绕过验证系统
                                            if bypass_validation:
                                                # 直接确认，不需要验证
                                                confirmed = True
                                                details = "条件D-技术独立 | 绕过验证系统"
                                                # 修复：确保使用条件D的reason（不保留其他条件的reason）
                                                # 如果sell_result中有tech_signal，优先使用tech_signal的reason
                                                if 'tech_signal' in sell_result and 'reason' in sell_result['tech_signal']:
                                                    sell_result['reason'] = sell_result['tech_signal']['reason']
                                            else:
                                                # 使用信号验证系统验证
                                                # 注意：如果策略信号中包含技术形态确认，会在reason中体现
                                                try:
                                                    # 检查是否有技术形态确认（用于影响验证系统）
                                                    tech_enhancement = sell_result.get('tech_enhancement', 0)
                                                    tech_confirmed = sell_result.get('tech_confirmed', False)
                                                    
                                                    # 技术形态增强等级配置（使用初始化时加载的配置）
                                                    if self.tech_enhancement_config is None:
                                                        # 配置未加载，禁用技术形态增强功能
                                                        logger.warning(f"⚠️ [{stock}] 技术形态增强配置未加载，跳过增强功能，执行正常验证")
                                                        confirmed, details = self.signal_checker.check_sell_signal(
                                                            stock_data=current_day_minute_data.iloc[:minute_idx+1],
                                                            current_index=minute_idx,
                                                            current_time=current_minute_time,
                                                            sell_reason=sell_result.get('reason', ''),
                                                            stock_code=stock,
                                                            position_info=position_data
                                                        )
                                                    else:
                                                        tech_bypass_threshold = self.tech_enhancement_config['bypass_threshold']
                                                        tech_relax_threshold = self.tech_enhancement_config['relax_threshold']
                                                        
                                                        if tech_confirmed and tech_enhancement >= tech_bypass_threshold:
                                                            # 技术形态达到bypass阈值，绕过验证系统
                                                            # 从配置读取评级描述
                                                            validation_impact = TECHNICAL_PATTERNS_CONFIG.get('signal_enhancement', {}).get('validation_impact', {})
                                                            rating_rules = validation_impact.get('rating_rules', [])
                                                            rating_label = next((r.get('label', '强烈确认') for r in rating_rules if tech_enhancement >= r.get('threshold', 0)), '强烈确认')
                                                            
                                                            # 改为DEBUG级别，减少日志噪音（详情在卖出原因中体现）
                                                            dedup_key = (date_str, stock, 'tech_bypass')
                                                            if dedup_key not in self.logged_confirmed_signals:
                                                                logger.debug(f"⚡ [{stock}] 技术形态{rating_label}(增强{tech_enhancement:.1%}≥{tech_bypass_threshold:.0%})，绕过验证系统")
                                                                self.logged_confirmed_signals[dedup_key] = 1
                                                            else:
                                                                logger.debug(f"⚡ [{stock}] 技术形态{rating_label}（重复）")
                                                            confirmed = True
                                                            details = f"技术形态{rating_label}(增强{tech_enhancement:.1%})，绕过验证"
                                                        elif tech_confirmed and tech_enhancement >= tech_relax_threshold:
                                                            # 技术形态中等确认（增强≥阈值），放宽验证要求
                                                            # 执行验证，但降低通过门槛
                                                            confirmed, details = self.signal_checker.check_sell_signal(
                                                                stock_data=current_day_minute_data.iloc[:minute_idx+1],
                                                                current_index=minute_idx,
                                                                current_time=current_minute_time,
                                                                sell_reason=sell_result.get('reason', ''),
                                                                stock_code=stock,
                                                                position_info=position_data
                                                            )
                                                            # 如果验证未通过，但技术形态达到relax阈值，放宽要求
                                                            if not confirmed:
                                                                # 从配置读取评级描述
                                                                validation_impact = TECHNICAL_PATTERNS_CONFIG.get('signal_enhancement', {}).get('validation_impact', {})
                                                                rating_rules = validation_impact.get('rating_rules', [])
                                                                rating_label = next((r.get('label', '确认') for r in rating_rules if tech_enhancement >= r.get('threshold', 0)), '确认')
                                                                
                                                                # 改为DEBUG级别，减少日志噪音（详情在卖出原因中体现）
                                                                dedup_key = (date_str, stock, 'tech_relax')
                                                                if dedup_key not in self.logged_confirmed_signals:
                                                                    logger.debug(f"📊 [{stock}] 技术形态{rating_label}(增强{tech_enhancement:.1%}≥{tech_relax_threshold:.0%})，放宽验证要求")
                                                                    self.logged_confirmed_signals[dedup_key] = 1
                                                                else:
                                                                    logger.debug(f"📊 [{stock}] 技术形态{rating_label}（重复）")
                                                                confirmed = True
                                                                details = f"{details} | 技术形态{rating_label}(增强{tech_enhancement:.1%})，放宽验证"
                                                        else:
                                                            # 正常验证流程（技术形态增强<阈值或无技术形态）
                                                            confirmed, details = self.signal_checker.check_sell_signal(
                                                                stock_data=current_day_minute_data.iloc[:minute_idx+1],
                                                                current_index=minute_idx,
                                                                current_time=current_minute_time,
                                                                sell_reason=sell_result.get('reason', ''),
                                                                stock_code=stock,
                                                                position_info=position_data
                                                            )
                                                except Exception as e:
                                                    # 验证系统异常时，放行信号（避免影响正常交易）
                                                    logger.warning(f"⚠️ {stock} 信号验证异常，放行信号: {str(e)}")
                                                    confirmed = True
                                                    details = f"验证异常，放行信号"
                                            
                                            # 处理验证结果（无论是绕过验证还是正常验证）
                                            if not confirmed:
                                                # 信号被过滤，记录日志但不执行卖出
                                                # 关键：不标记sell_stocks，继续检查后续分钟
                                                self._log_filtered_signal(stock, details, date_str)
                                                # 信号被过滤时，不continue，继续处理（可能技术形态会触发）
                                                # 如果技术形态也不触发，当前分钟不执行卖出，但下分钟继续检查
                                                sell_result['should_sell'] = False  # 标记为不卖出，继续后续逻辑
                                            else:
                                                # 信号确认通过，记录详情（格式化为多行显示）
                                                # 先检查是否会T+1拦截，如果会则简化日志
                                                position_info = self.position_batches.get(stock, {})
                                                batches = position_info.get('batches', [])
                                                will_t1_intercept = False
                                                if batches:
                                                    first_batch = batches[0]
                                                    buy_time = first_batch.get('time', '')  # '2025-02-07 09:57:00'
                                                    buy_date = buy_time.split()[0].replace('-', '')  # '20250207'
                                                    will_t1_intercept = (buy_date == date_str)
                                                
                                                self._log_confirmed_signal(stock, details, date_str, will_t1_intercept)
                                                # 更新卖出原因，只添加验证状态（去掉重复的原因部分）
                                                # 条件D绕过验证，不追加验证信息（reason已经包含完整信息）
                                                if not bypass_validation:
                                                    parts = details.split(' | ', 1)  # 只分割第一个 |
                                                    validation_info = parts[1] if len(parts) > 1 else details
                                                    sell_result['reason'] = f"{sell_result.get('reason', '')} | {validation_info}"
                                        
                                        # 只有实际执行卖出后，才标记sell_stocks
                                        if sell_result.get('should_sell', False):
                                            # T+1规则检查：今天买的股票今天不能卖
                                            position_info = self.position_batches.get(stock, {})
                                            batches = position_info.get('batches', [])
                                            
                                            # 简化T+1检查：直接比较日期字符串
                                            skip_t1 = False
                                            if batches:
                                                first_batch = batches[0]
                                                buy_time = first_batch.get('time', '')  # '2025-02-07 09:57:00'
                                                buy_date = buy_time.split()[0].replace('-', '')  # '20250207'
                                                
                                                if buy_date == date_str:  # 直接比较 '20250207' == '20250207'
                                                    # T+1规则拦截日志去重：同一天同一股票只输出一次
                                                    t1_key = (date_str, stock)
                                                    if t1_key not in self.logged_t1_intercepts:
                                                        t1_msg = f"🚫 {stock} T+1规则拦截：买入日期{buy_date}，当前日期{date_str}"
                                                        logger.info(t1_msg)
                                                        # 同时记录到回测日志文件（如果存在）
                                                        if hasattr(self.strategy_wrapper.strategy_instance, 'signal_log_file') and self.strategy_wrapper.strategy_instance.signal_log_file:
                                                            try:
                                                                self.strategy_wrapper.strategy_instance.signal_log_file.write(f"{t1_msg}\n")
                                                                self.strategy_wrapper.strategy_instance.signal_log_file.flush()
                                                            except:
                                                                pass
                                                        self.logged_t1_intercepts.add(t1_key)
                                                    skip_t1 = True
                                            
                                            if not skip_t1:
                                                # 执行卖出操作（支持分批减仓）
                                                sell_ratio = sell_result.get('sell_ratio', 1.0)  # 默认全部清仓
                                                self._execute_sell(stock, current_day_minute_data.iloc[minute_idx], sell_result.get('reason', ''), sell_ratio, sell_result)
                                                # 只有实际执行卖出后，才标记sell_stocks（避免后续分钟重复卖出）
                                                self.strategy_wrapper.strategy_instance.sell_stocks.add(stock)
                                                sell_signals += 1
                                            else:
                                                # T+1规则拦截，不执行卖出，也不标记sell_stocks（允许后续检查）
                                                # 已在上面的T+1检查中输出日志，这里不再重复输出
                                                pass
                            
                            # 只有当有交易信号时才输出日志
                            if buy_signals > 0 or sell_signals > 0:
                                # 先换行确保交易信号不被进度覆盖
                                print()  # 换行
                                # 获取股票名称
                                stock_name = ""
                                try:
                                    detail = xtdata.get_instrument_detail(stock)
                                    if isinstance(detail, dict):
                                        stock_name = detail.get('InstrumentName', '')
                                except:
                                    pass
                        except Exception as e:
                            # 获取股票名称
                            stock_name = ""
                            try:
                                detail = xtdata.get_instrument_detail(stock)
                                if isinstance(detail, dict):
                                    stock_name = detail.get('InstrumentName', '')
                            except:
                                pass
                            display_name = f"{stock} ({stock_name})" if stock_name else stock
                            logger.error(f"回测股票 {display_name} 时出错: {str(e)}")
                            logger.debug(f"详细错误信息: {repr(e)}", exc_info=True)
                    
                    # 每天结束时记录当日资金占用（用于计算时间加权平均使用资金）
                    daily_used = self._calculate_daily_capital_usage()
                    self.daily_capital_usage.append({
                        'date': date_str,
                        'capital_used': daily_used
                    })
                
                current_date += timedelta(days=1)
            
            # 回测完成后换行
            print()  # 换行
            
            # 记录实际的最后交易日
            if valid_stocks and valid_stocks[0] in self.stock_data:
                last_data_date = self.stock_data[valid_stocks[0]].index[-1].strftime('%Y%m%d')
                logger.info(f"数据最后交易日: {last_data_date}")
                if last_data_date != end_date:
                    logger.warning(f"⚠️  最后交易日 {last_data_date} 早于回测结束日期 {end_date}，说明期间无交易日（可能是节假日）")
            
            # 强制平仓（回测结束时）
            self._force_close_all_positions(end_date)
            
            # 计算回测结果
            results['final_capital'] = self.current_capital
            results['total_return'] = (self.current_capital - self.initial_capital) / self.initial_capital * 100
            results['trades'] = self.trades
            results['positions'] = self.positions
            results['warmup_start_date'] = warmup_start_date  # K线图从此日期开始显示（不显示预热期）
            
            # 添加调试信息
            logger.debug(f"回测完成: trades数量={len(self.trades)}, positions数量={len(self.positions)}")
            logger.debug(f"当前资金: {self.current_capital}, 初始资金: {self.initial_capital}")
            if self.trades:
                for i, trade in enumerate(self.trades):
                    logger.debug(f"  trade {i}: {trade}")
            else:
                logger.debug("  没有交易记录")
            if self.positions:
                for stock, position in self.positions.items():
                    logger.debug(f"  position {stock}: {position}")
            else:
                logger.debug("  没有持仓记录")
            
            # 添加更多调试信息
            logger.debug(f"最终统计: 初始资金={self.initial_capital}, 最终资金={self.current_capital}, 交易次数={len(self.trades)}")
            
            # 检查是否有交易执行
            if len(self.trades) == 0:
                logger.info("==================================================")
                logger.info("回测期间没有执行任何交易，可能的原因：")
                logger.info("1. 策略条件检查未通过（没有满足买入条件）")
                logger.info("2. 价格过高导致资金不足")
                logger.info("3. 数据质量问题")
                logger.info("4. 预热期设置导致回测期间过短")
                logger.info("==================================================")
            
            # 计算剔除强制平仓后的最终资金（与回测报告一致）
            forced_trades = [t for t in self.trades if t.get('is_forced_close', False)]
            forced_profit = sum(t.get('profit', 0) for t in forced_trades)
            final_capital_excluding_forced = self.current_capital - forced_profit
            total_return_excluding_forced = ((final_capital_excluding_forced - self.initial_capital) / self.initial_capital) * 100
            
            logger.info(f"回测完成: 初始资金 {self.initial_capital:,.2f}, 最终资金 {final_capital_excluding_forced:,.2f}, 总资产收益率 {total_return_excluding_forced:+.2f}%")
            logger.info(f"分钟级回测完成")
            
            return results
            
        except Exception as e:
            logger.error(f"回测过程中出错: {str(e)}")
            logger.debug(f"详细错误信息: {repr(e)}", exc_info=True)
            raise

    def _force_close_all_positions(self, end_date: str):
        """强制平仓所有持仓
        
        Args:
            end_date: 回测结束日期 (YYYYMMDD格式)
        """
        for stock in list(self.positions.keys()):
            try:
                if stock in self.stock_data and not self.stock_data[stock].empty:
                    # 修复：优先使用回测结束日期的数据，如果没有则使用最后一条交易日数据
                    end_datetime = pd.to_datetime(end_date)
                    stock_minute_data = self.stock_data[stock]
                    
                    # 尝试找到回测结束日期当天的最后一条分钟数据
                    end_day_data = stock_minute_data[stock_minute_data.index.date == end_datetime.date()]
                    
                    if not end_day_data.empty:
                        # 如果回测结束日期有数据，使用该日最后一条（15:00收盘）
                        last_minute = end_day_data.iloc[-1]
                        logger.info(f"强制平仓 {stock}: 使用回测结束日期 {end_date} 的收盘数据")
                    else:
                        # 如果回测结束日期没有数据（非交易日），使用最后一个交易日的数据
                        last_minute = stock_minute_data.iloc[-1]
                        last_trade_date = last_minute.name.strftime('%Y%m%d') if hasattr(last_minute.name, 'strftime') else str(last_minute.name)
                        logger.warning(f"强制平仓 {stock}: 回测结束日期 {end_date} 无数据，使用最后交易日 {last_trade_date} 的数据")
                    
                    self._execute_sell(stock, last_minute, f"回测结束强制平仓", 1.0, None)
                else:
                    logger.warning(f"无法强制平仓 {stock}：没有分钟数据")
                    
            except Exception as e:
                logger.error(f"强制平仓 {stock} 时出错: {str(e)}")

    def _get_batch_config(self) -> dict:
        """
        从配置文件读取批次配置
        返回: {
            'total_batches': 2,  # 总批次数
            'ratios': [0.70, 0.30],  # 各批次比例
            'enabled': True  # 是否启用加仓
        }
        """
        try:
            pos_calc_config = self.backtest_config.get('position_calculator', {})
            batch_ratios = pos_calc_config.get('batch_ratios', {})
            add_conditions = pos_calc_config.get('add_position_conditions', {})
            
            # 读取批次比例
            ratios = []
            if 'first' in batch_ratios:
                ratios.append(batch_ratios['first'])
            if 'second' in batch_ratios:
                ratios.append(batch_ratios['second'])
            
            # 过滤掉比例为0的批次
            ratios = [r for r in ratios if r > 0]
            
            return {
                'total_batches': len(ratios),
                'ratios': ratios,
                'enabled': add_conditions.get('enabled', False)
            }
        except Exception as e:
            logger.warning(f"读取批次配置失败: {str(e)}，使用默认配置")
            return {
                'total_batches': 1,
                'ratios': [1.0],
                'enabled': False
            }

    def _check_add_position_conditions(self, stock: str, minute_data) -> tuple:
        """
        检查是否满足加仓条件（下一批买入）
        
        所有参数从配置文件读取：
        1. enabled: 是否启用加仓功能
        2. min_price_increase: 最小价格涨幅（%）
        3. max_price_increase: 最大价格涨幅（%）
        4. require_strategy_signal: 是否要求仍满足策略买入条件
        5. min_capital_ratio: 最小资金比例要求
        6. batch_ratios: 批次比例配置（动态读取，不硬编码批次数）
        
        Returns:
            (should_add: bool, reason: str)
        """
        try:
            # 从配置文件读取加仓条件和批次配置
            add_conditions = self.backtest_config.get('position_calculator', {}).get('add_position_conditions', {})
            batch_config = self._get_batch_config()
            
            # 检查是否启用加仓功能
            if not add_conditions.get('enabled', False):
                return False, "加仓功能未启用"
            
            if stock not in self.position_batches:
                return False, "无批次信息"
            
            # 读取所有参数（从配置文件）
            min_increase = add_conditions.get('min_price_increase', 3.0)
            max_increase = add_conditions.get('max_price_increase', 8.0)
            require_strategy = add_conditions.get('require_strategy_signal', True)
            min_capital_ratio = add_conditions.get('min_capital_ratio', 0.01)
            
            batch_info = self.position_batches[stock]
            first_buy_price = batch_info.get('first_buy_price', 0)
            current_price = minute_data['close']
            current_batch = batch_info.get('current_batch', 1)
            
            if first_buy_price <= 0:
                return False, "无效的首次买入价"
            
            # 条件1：价格上涨幅度检查（从配置读取）
            price_change_pct = (current_price - first_buy_price) / first_buy_price * 100
            
            if price_change_pct < min_increase:
                return False, f"涨幅不足({price_change_pct:.2f}% < {min_increase}%)"
            
            if price_change_pct > max_increase:
                return False, f"涨幅过大({price_change_pct:.2f}% > {max_increase}%)，错过加仓时机"
            
            # 条件2：可选的策略条件检查（从配置读取）
            if require_strategy and not self.strategy_wrapper.check_conditions(stock):
                return False, "不满足策略条件"
            
            # 条件3：资金检查（动态计算下一批次比例，不硬编码）
            # 从配置读取批次比例
            ratios = batch_config['ratios']
            
            # 计算下一批次的比例
            if current_batch >= len(ratios):
                return False, f"已完成所有{len(ratios)}批建仓"
            
            next_batch_ratio = ratios[current_batch]  # 下一批次的比例（current_batch是0-based index）
            
            if next_batch_ratio <= 0:
                return False, f"第{current_batch + 1}批比例为0"
            
            # 计算下一批次需要的资金
            next_batch_value = self.current_capital * next_batch_ratio
            min_required = 100 * current_price * (1 + self.commission_rate)
            
            if next_batch_value < min_required:
                return False, f"资金不足(需要{min_required:.2f}，可用{next_batch_value:.2f})"
            
            # 所有条件满足
            reason = f"价格从{first_buy_price:.2f}涨至{current_price:.2f}(+{price_change_pct:.2f}%)，满足加仓条件"
            return True, reason
            
        except Exception as e:
            logger.error(f"检查加仓条件时出错: {str(e)}")
            return False, f"检查异常: {str(e)}"
    
    def _execute_add_position(self, stock: str, minute_data):
        """
        执行下一批加仓（从配置文件动态读取批次比例）
        """
        try:
            if stock not in self.position_batches:
                logger.warning(f"无法加仓{stock}: 无批次信息")
                return
            
            # 从配置读取批次信息
            batch_config = self._get_batch_config()
            batch_info = self.position_batches[stock]
            current_batch = batch_info.get('current_batch', 1)
            ratios = batch_config['ratios']
            
            # 动态计算下一批次比例（不硬编码）
            if current_batch >= len(ratios):
                logger.warning(f"无法加仓{stock}: 已完成所有{len(ratios)}批建仓")
                return
            
            next_batch_ratio = ratios[current_batch]  # 下一批次的比例
            
            if next_batch_ratio <= 0:
                logger.warning(f"无法加仓{stock}: 第{current_batch + 1}批比例为0")
                return
            
            price = minute_data['close']
            
            # 计算滑点
            daily_data = self.daily_data.get(stock, pd.DataFrame())
            hist_data = {
                'high': daily_data['high'].values if 'high' in daily_data and not daily_data.empty else None,
                'low': daily_data['low'].values if 'low' in daily_data and not daily_data.empty else None,
                'close': daily_data['close'].values if 'close' in daily_data and not daily_data.empty else None
            }
            stock_today_data = self.strategy_wrapper.strategy_instance.today_data.get(stock, {})
            market_cap = stock_today_data.get('market_cap', 0)
            slippage_rate = self.slippage_calculator.calculate_slippage_rate(
                stock_code=stock,
                current_price=price,
                hist_data=hist_data,
                market_cap=market_cap
            )
            
            actual_price = self.slippage_calculator.apply_slippage(price, slippage_rate, 'buy')
            
            # 计算下一批次买入数量（从配置读取比例）
            next_batch_value = self.current_capital * next_batch_ratio
            buy_shares = int(next_batch_value / (actual_price * (1 + self.commission_rate)) / 100) * 100
            
            if buy_shares < 100:
                logger.warning(f"无法加仓{stock}: 资金不足买入100股")
                return
            
            # 计算成本
            buy_amount = buy_shares * actual_price
            commission = buy_amount * self.commission_rate
            total_cost = buy_amount + commission
            
            if total_cost > self.current_capital:
                # 资金不足，重新计算
                buy_shares = int(self.current_capital / (actual_price * (1 + self.commission_rate)) / 100) * 100
                if buy_shares < 100:
                    logger.warning(f"无法加仓{stock}: 资金完全不足")
                    return
                buy_amount = buy_shares * actual_price
                commission = buy_amount * self.commission_rate
                total_cost = buy_amount + commission
            
            # 在执行买入前，触发策略的check_conditions以输出完整日志
            # （前提是主循环已经临时清理了mock_positions，且数据已准备好）
            try:
                # 动态设置当前批次号（用于日志显示"第X批/共Y批"）
                next_batch_num = current_batch + 1
                if hasattr(self.strategy_wrapper, 'strategy_instance'):
                    self.strategy_wrapper.strategy_instance.current_batch_num = next_batch_num
                
                # 调用check_conditions触发完整的买入日志
                # 此时mock_positions已被主循环临时清理，所以会输出"满足条件股票"日志
                self.strategy_wrapper.check_conditions(stock)
                
                # 清除临时批次号
                if hasattr(self.strategy_wrapper, 'strategy_instance'):
                    if hasattr(self.strategy_wrapper.strategy_instance, 'current_batch_num'):
                        delattr(self.strategy_wrapper.strategy_instance, 'current_batch_num')
            except Exception as e:
                logger.debug(f"触发加仓日志时出错: {str(e)}")
            
            # 执行加仓（复用买入逻辑，会自动更新批次号）
            self._execute_buy(stock, minute_data)
            
            logger.info(f"🔴 成功加仓: {stock} 第{next_batch_num}批 {buy_shares}股 @ {actual_price:.2f}元")
            
        except Exception as e:
            logger.error(f"执行加仓时出错: {str(e)}")

    def _arbitrate_sell_signals(self, 
                                strategy_signal: Dict, 
                                tech_signals: List[Dict],
                                stock: str) -> Dict:
        """
        信号仲裁：合并策略信号和技术信号，选择优先级最高的
        
        优先级规则：
        1. 技术紧急信号（优先级9）> 策略信号（优先级6）
        2. 技术强信号（优先级7）> 策略信号（优先级6）
        3. 策略信号（优先级6）> 技术弱信号（优先级5）
        
        新增规则：
        4. 强度≥3.0的技术信号直接执行，绕过验证器（避免在高敏感期被过滤）
        
        Args:
            strategy_signal: 策略卖出信号 {'should_sell': bool, 'reason': str, 'sell_ratio': float}
            tech_signals: 技术形态信号列表（已按优先级排序）
            stock: 股票代码
        
        Returns:
            最终卖出信号
        """
        from technical_engine import SignalPriority
        
        # 优先检查技术强信号（评分≥3.0），直接执行，绕过验证器
        if tech_signals:
            for signal in tech_signals:
                strength = signal.get('strength', 0)
                priority = signal.get('priority', 0)
                signal_type = signal.get('signal_type', '')
                
                # 只处理卖出信号
                if signal_type != 'sell':
                    continue
                
                # 强度≥3.5 或 优先级≥9：紧急信号，立即执行
                if strength >= 3.5 or priority >= 9:
                    logger.warning(f"⚡ [{stock}] 紧急技术信号！评分{strength:.1f} 优先级{priority} - 立即执行，绕过验证")
                    return {
                        'should_sell': True,
                        'reason': signal.get('reason', '紧急技术信号'),
                        'sell_ratio': signal.get('sell_ratio', 1.0),
                        'source': 'emergency_technical',
                        'priority': priority,
                        'strength': strength,
                        'urgency': 'emergency',
                        'bypass_verification': True  # 关键：标记绕过验证
                    }
                
                # 强度≥3.0 或 优先级≥7：强信号，立即执行
                elif strength >= 3.0 or priority >= 7:
                    logger.warning(f"🔥 [{stock}] 强技术信号（评分{strength:.1f} 优先级{priority}）- 立即执行，绕过验证")
                    return {
                        'should_sell': True,
                        'reason': signal.get('reason', '强技术信号'),
                        'sell_ratio': signal.get('sell_ratio', 1.0),
                        'source': 'strong_technical',
                        'priority': priority,
                        'strength': strength,
                        'urgency': signal.get('urgency', 'high'),
                        'bypass_verification': True  # 关键：标记绕过验证
                    }
        
        # 默认不卖出
        final_signal = {'should_sell': False, 'reason': '', 'sell_ratio': 1.0, 'source': 'none'}
        
        # 策略信号优先级（固定为6）
        strategy_priority = SignalPriority.STRATEGY_SELL if strategy_signal.get('should_sell') else 0
        
        # 技术信号优先级（取最高的）
        tech_priority = 0
        best_tech_signal = None
        if tech_signals:
            # 只考虑卖出信号
            sell_tech_signals = [s for s in tech_signals if s.get('signal_type') == 'sell']
            if sell_tech_signals:
                best_tech_signal = sell_tech_signals[0]  # 已按优先级排序
                tech_priority = best_tech_signal.get('priority', 0)
        
        # 比较优先级，选择最高的
        if tech_priority > strategy_priority:
            # 技术信号优先级更高
            final_signal = {
                'should_sell': True,
                'reason': best_tech_signal.get('reason', '技术形态卖出'),
                'sell_ratio': best_tech_signal.get('sell_ratio', 1.0),
                'source': 'technical',
                'priority': tech_priority,
                'urgency': best_tech_signal.get('urgency', 'normal')
            }
            logger.info(f"[{stock}] 信号仲裁：技术信号优先（优先级{tech_priority} > {strategy_priority}）")
        
        elif strategy_priority > 0:
            # 策略信号触发
            final_signal = {
                'should_sell': True,
                'reason': strategy_signal.get('reason', '策略卖出'),
                'sell_ratio': strategy_signal.get('sell_ratio', 1.0),
                'source': 'strategy',
                'priority': strategy_priority
            }
            
            # 如果有技术信号支持，添加到原因中
            if best_tech_signal:
                tech_reason = best_tech_signal.get('reason', '')
                final_signal['reason'] += f" + 技术形态: {tech_reason}"
                logger.info(f"[{stock}] 信号仲裁：策略信号 + 技术辅助")
            # else: 移除常规策略信号的日志输出（减少冗余，因为后续验证系统会输出详细信息）
        
        else:
            # 无卖出信号（降级为debug，减少日志量）
            logger.debug(f"[{stock}] 信号仲裁：无卖出信号")
        
        return final_signal
    
    def _log_filtered_signal(self, stock: str, details: str, date_str: str = None, current_time: str = None):
        """
        格式化输出被过滤的信号（多行显示，保留完整条件）
        实现去重：同一天同一股票同一策略条件，每天最多输出3次（开盘、中午、收盘）
        :param stock: 股票代码
        :param details: 信号详情字符串
        :param date_str: 当前日期（YYYYMMDD格式）
        :param current_time: 当前时间（用于判断是否需要输出）
        """
        # 获取日期（如果未传入，尝试从上下文获取）
        if date_str is None:
            # 尝试从logged_filtered_signals的key中获取最新日期
            if self.logged_filtered_signals:
                date_str = max(k[0] for k in self.logged_filtered_signals.keys())
            else:
                date_str = 'unknown'
        
        # 提取原因的核心部分（策略条件名称），去除价格等变化的部分
        # 例如："(高位强势策略) 条件B1-大盘股天量出货: 突破前高94.82 + 当前105.61" 
        # -> "(高位强势策略) 条件B1-大盘股天量出货"
        try:
            parts = details.split(' | ')
            sell_reason = parts[0] if parts else details
            # 提取策略条件名称（例如"条件B1-大盘股天量出货"）
            if '条件' in sell_reason:
                # 找到"条件"后面的内容，直到第一个":"或"+"之前
                condition_start = sell_reason.find('条件')
                if condition_start >= 0:
                    condition_part = sell_reason[condition_start:]
                    # 找到第一个":"或"+"的位置
                    colon_pos = condition_part.find(':')
                    plus_pos = condition_part.find(' +')
                    if colon_pos > 0:
                        condition_key = condition_part[:colon_pos].strip()
                    elif plus_pos > 0:
                        condition_key = condition_part[:plus_pos].strip()
                    else:
                        condition_key = condition_part.split()[0] if condition_part.split() else sell_reason
                else:
                    condition_key = sell_reason
            else:
                condition_key = sell_reason
        except:
            condition_key = details
        
        # 使用核心原因作为去重key（而不是完整details）
        log_key = (date_str, stock, condition_key)
        
        # 检查该策略条件今天已输出的次数
        if log_key not in self.logged_filtered_signals:
            self.logged_filtered_signals[log_key] = {'count': 0, 'last_time': 0}
        
        log_info = self.logged_filtered_signals[log_key]
        count = log_info['count']
        last_time = log_info['last_time']
        
        # 每天最多输出3次：开盘(09:30-10:30)、中午(11:00-13:00)、收盘(14:30-15:00)
        import time
        current_timestamp = time.time()
        hours_since_last = (current_timestamp - last_time) / 3600 if last_time > 0 else 999
        
        # 如果今天已经输出3次，或者距离上次输出不到2小时，则不输出
        if count >= 3 or (last_time > 0 and hours_since_last < 2.0):
            logger.debug(f"⏳ {stock} 卖出信号被过滤（{condition_key}，今天已输出{count}次，略）")
            return
        
        # 更新计数和时间
        log_info['count'] = count + 1
        log_info['last_time'] = current_timestamp
        
        # 先换行确保日志不被进度条覆盖
        print()
        
        def write_to_log(msg):
            """同时输出到logger和signal_log_file"""
            logger.info(msg)
            if hasattr(self.strategy_wrapper.strategy_instance, 'signal_log_file') and self.strategy_wrapper.strategy_instance.signal_log_file:
                try:
                    self.strategy_wrapper.strategy_instance.signal_log_file.write(f"{msg}\n")
                    self.strategy_wrapper.strategy_instance.signal_log_file.flush()
                except:
                    pass
        
        try:
            # 解析details字符串：原因 | 敏感度 | 确认数 | 置信度 | 验证器结果
            parts = details.split(' | ')
            if len(parts) < 4:
                # 如果格式不符合预期，直接输出原始内容
                write_to_log(f"⏳ {stock} 卖出信号被过滤: {details}")
                return
            
            sell_reason = parts[0]  # 完整的卖出原因
            sensitivity_info = parts[1]  # 敏感度信息
            confirmation_info = parts[2]  # 确认信息
            confidence_info = parts[3]  # 置信度信息
            validator_results = ' | '.join(parts[4:]) if len(parts) > 4 else ''  # 验证器结果
            
            # 多行格式输出
            write_to_log(f"⏳ {stock} 卖出信号被过滤 [{sensitivity_info} {confirmation_info} {confidence_info}]")
            write_to_log(f"   原因: {sell_reason}")
            if validator_results:
                write_to_log(f"   结果: {validator_results}")
        except Exception as e:
            # 异常时回退到原始输出
            write_to_log(f"⏳ {stock} 卖出信号被过滤: {details}")

    def _log_confirmed_signal(self, stock: str, details: str, date_str: str = None, will_t1_intercept: bool = False):
        """
        格式化输出确认的信号（多行显示，保留完整条件）
        如果将被T+1拦截，降低日志级别或简化输出
        :param stock: 股票代码
        :param details: 信号详情字符串
        :param date_str: 当前日期（YYYYMMDD格式）
        :param will_t1_intercept: 是否将被T+1规则拦截
        """
        def write_to_log(msg, level='info'):
            """同时输出到logger和signal_log_file"""
            if level == 'info':
                logger.info(msg)
            else:
                logger.debug(msg)  # T+1拦截的情况降级为debug
            if hasattr(self.strategy_wrapper.strategy_instance, 'signal_log_file') and self.strategy_wrapper.strategy_instance.signal_log_file:
                try:
                    self.strategy_wrapper.strategy_instance.signal_log_file.write(f"{msg}\n")
                    self.strategy_wrapper.strategy_instance.signal_log_file.flush()
                except:
                    pass
        
        # 如果将被T+1拦截，简化输出或降级为debug
        if will_t1_intercept:
            logger.debug(f"🟢 {stock} 卖出信号确认（但T+1拦截，略）")
            return
        
        # 先换行确保日志不被进度条覆盖（但不重复打印，如果外层已经打印过）
        # 注意：在交易信号块中已经有了换行（第1777行），这里暂不重复添加
        # 如果发现日志仍然被覆盖，取消注释下面这行：
        # print()
        
        try:
            # 解析details字符串
            parts = details.split(' | ')
            if len(parts) < 2:
                # 如果是简单格式（如"正常时段直接执行"），直接输出
                write_to_log(f"🟢 {stock} 卖出信号确认: {details}")
                return
            
            sell_reason = parts[0]  # 完整的卖出原因
            
            # 检查是否是条件D（技术独立触发）
            if '条件D-技术独立' in details:
                write_to_log(f"🟢 {stock} 卖出信号确认 [条件D-技术独立]")
            # 检查是否是正常时段（无需验证）
            elif '正常时段直接执行' in details:
                # 简化输出，原因会在后续_execute_sell中详细显示
                write_to_log(f"🟢 {stock} 卖出信号确认 [正常时段]")
            elif len(parts) >= 4:
                # 有验证信息的情况
                sensitivity_info = parts[1]  # 敏感度信息
                confirmation_info = parts[2]  # 确认信息
                confidence_info = parts[3]  # 置信度信息
                validator_results = ' | '.join(parts[4:]) if len(parts) > 4 else ''  # 验证器结果
                
                # 简化输出：只显示验证状态，原因会在后续_execute_sell中详细显示
                write_to_log(f"🟢 {stock} 卖出信号确认 [{sensitivity_info} {confirmation_info} {confidence_info}]")
                if validator_results:
                    write_to_log(f"   验证: {validator_results}")
            else:
                # 其他格式，直接输出
                write_to_log(f"🟢 {stock} 卖出信号确认: {details}")
        except Exception as e:
            # 异常时回退到原始输出
            write_to_log(f"🟢 {stock} 卖出信号确认: {details}")

    def _execute_buy(self, stock: str, minute_data):
        """
        执行买入操作
        :param stock: 股票代码
        :param minute_data: 当前分钟数据
        """
        try:
            logger.debug(f"开始执行买入操作: {stock}")
            
            # 获取价格数据（minute_data 是 pandas Series）
            price = minute_data['close']
            
            logger.debug(f"计算买入参数: price={price}, current_capital={self.current_capital}")
            
            if price <= 0:
                logger.debug(f"无法买入 {stock}: 价格无效 (price={price})")
                return
            
            # 检查是否能买入至少100股
            min_shares_cost = 100 * price * (1 + self.commission_rate)
            if min_shares_cost > self.current_capital:
                logger.debug(f"无法买入 {stock}: 资金不足买入最少100股 (需要{min_shares_cost:.2f}, 当前资金{self.current_capital:.2f})")
                return
            
            # 使用仓位管理器计算目标仓位
            try:
                # 获取仓位计算器
                position_calculator = self.strategy_wrapper.strategy_instance.position_calculator
                
                # 获取策略类型（用于计算仓位）
                strategy_type_name = getattr(self.strategy_wrapper.strategy_instance, 'last_strategy_type', '高位强势策略')
                # 映射到PositionCalculator识别的类型
                strategy_type = 'low' if '低位' in strategy_type_name else 'high'
                
                # 获取股票的today_data以获取市值、量比等信息
                stock_today_data = self.strategy_wrapper.strategy_instance.today_data.get(stock, {})
                
                # 获取股票类型（大/中/小盘）
                market_cap = stock_today_data.get('market_cap', 0)
                if market_cap > 200:
                    stock_type = 'large'
                elif market_cap >= 50:
                    stock_type = 'mid'
                else:
                    stock_type = 'small'
                
                # 计算目标仓位比例和第一批买入比例
                target_position_ratio = position_calculator.calculate_target_position(
                    strategy_type=strategy_type,
                    stock_type=stock_type,
                    volume_strength=stock_today_data.get('volume_strength', 'normal')
                )
                
                # 计算第一批买入比例（回测系统使用第一批比例，不是总目标仓位）
                first_batch_ratio = position_calculator.calculate_first_batch_position(target_position_ratio)
                
                # 保存计划仓位比例，用于后续日志输出
                if not hasattr(self, '_planned_position_ratios'):
                    self._planned_position_ratios = {}
                self._planned_position_ratios[stock] = first_batch_ratio
                
                logger.debug(f"仓位计算: strategy_type={strategy_type}, target={target_position_ratio:.2%}, first_batch={first_batch_ratio:.2%}, market_cap={stock_today_data.get('market_cap', 0):.2f}亿")
                
            except Exception as e:
                # 如果计算失败，使用默认仓位比例
                logger.warning(f"使用仓位管理器计算失败: {str(e)}，使用默认仓位比例")
                first_batch_ratio = self.single_position_ratio
                
                # 保存计划仓位比例
                if not hasattr(self, '_planned_position_ratios'):
                    self._planned_position_ratios = {}
                self._planned_position_ratios[stock] = first_batch_ratio
            
            # 先计算滑点率和含滑点价格
            daily_data = self.daily_data.get(stock, pd.DataFrame())
            hist_data = {
                'high': daily_data['high'].values if 'high' in daily_data and not daily_data.empty else None,
                'low': daily_data['low'].values if 'low' in daily_data and not daily_data.empty else None,
                'close': daily_data['close'].values if 'close' in daily_data and not daily_data.empty else None
            }
            market_cap = stock_today_data.get('market_cap', 0) if hasattr(self, 'strategy_wrapper') and self.strategy_wrapper else 0
            slippage_rate = self.slippage_calculator.calculate_slippage_rate(
                stock_code=stock,
                current_price=price,
                hist_data=hist_data,
                market_cap=market_cap
            )
            
            # 应用滑点：买入时价格上浮
            actual_buy_price = self.slippage_calculator.apply_slippage(price, slippage_rate, 'buy')
            
            # 计算第一批买入的仓位价值（使用first_batch_ratio而不是target_position_ratio）
            max_position_value = self.current_capital * first_batch_ratio
            
            # ✅ 修复：基于含滑点的价格计算买入数量（而不是理论价格）
            # 这样可以确保实际成本不会超出预算
            buy_shares = int(max_position_value / (actual_buy_price * (1 + self.commission_rate)) / 100) * 100
            
            # 如果按最大仓位限制买不起，则尝试买入最少的100股
            if buy_shares <= 0:
                buy_shares = 100
            
            # 确保买入数量至少是100股的整数倍
            if buy_shares < 100:
                buy_shares = 100
            
            # 计算实际成本（使用含滑点的价格）
            buy_amount = buy_shares * actual_buy_price
            commission = buy_amount * self.commission_rate
            total_cost = buy_amount + commission
            
            # 检查资金是否足够
            if total_cost > self.current_capital:
                # 资金不足，重新计算能买入的最大数量（包含滑点）
                buy_shares = int(self.current_capital / (actual_buy_price * (1 + self.commission_rate)) / 100) * 100
                if buy_shares <= 0:
                    logger.debug(f"无法买入 {stock}: 资金不足 (current_capital={self.current_capital}, total_cost={total_cost})")
                    return
                buy_amount = buy_shares * actual_buy_price
                commission = buy_amount * self.commission_rate
                total_cost = buy_amount + commission
            
            logger.debug(f"实际买入参数: buy_shares={buy_shares}, buy_amount={buy_amount}, commission={commission}, total_cost={total_cost}")
            
            # 更新资金和持仓
            self.current_capital -= total_cost
            
            # 动态计算批次号（基于是否已有批次追踪信息）
            if stock in self.position_batches:
                # 已有批次记录，计算下一批次号
                current_batch_num = self.position_batches[stock].get('current_batch', 1)
                next_batch_num = current_batch_num + 1
            else:
                # 首次买入
                next_batch_num = 1
            
            if stock in self.positions:
                self.positions[stock] += buy_shares
            else:
                self.positions[stock] = buy_shares
            
            logger.debug(f"资金和持仓更新完成: current_capital={self.current_capital}, positions[{stock}]={self.positions[stock]}, batch_num={next_batch_num}")
            
            # 获取交易时间
            trade_time = minute_data.name.strftime('%Y-%m-%d %H:%M:%S')
            
            # 获取策略类型并构建详细买入原因
            try:
                # 获取today_data中的关键指标
                stock_today_data = self.strategy_wrapper.strategy_instance.today_data.get(stock, {})
                volume_ratio = stock_today_data.get('volRatio', 0)
                
                # 直接使用today_data中已缓存的changePct（由prepare_stock_data计算并缓存）
                price_change_pct = stock_today_data.get('changePct', 0) * 100  # 转为百分比
                
                # 获取策略类型
                strategy_type = self.strategy_wrapper.strategy_instance.last_strategy_type or "未知策略"
                
                # 构建详细买入原因（显示实际值 vs 配置阈值）
                buy_reason_parts = [f"({strategy_type}) 买入条件："]
                buy_reason_parts.append(f"突破5日线")
                buy_reason_parts.append(f"量比{volume_ratio:.2f}")
                if price_change_pct > 0:
                    buy_reason_parts.append(f"涨幅+{price_change_pct:.2f}%")
                else:
                    buy_reason_parts.append(f"涨幅{price_change_pct:.2f}%")
                buy_reason_parts.append("多头趋势")
                buy_reason_parts.append("量价配合")
                
                buy_reason = " + ".join(buy_reason_parts)
            except Exception as e:
                # 如果获取失败，使用默认格式
                buy_reason = "买入条件：突破5日线 + 多头趋势 + 量价配合"
                try:
                    if hasattr(self.strategy_wrapper, 'strategy_instance') and hasattr(self.strategy_wrapper.strategy_instance, 'last_strategy_type'):
                        st = self.strategy_wrapper.strategy_instance.last_strategy_type
                        if st:
                            buy_reason = f"({st}) {buy_reason}"
                except:
                    pass
            
            # 记录详细的批次信息（批次号从配置动态计算，不硬编码）
            batch_info = {
                'batch_num': next_batch_num,
                'shares': buy_shares,
                'price': actual_buy_price,
                'time': trade_time,
                'amount': total_cost
            }
            
            if next_batch_num == 1:
                # 第一批买入：初始化批次追踪
                self.position_batches[stock] = {
                    'batches': [batch_info],
                    'target_position': target_position_ratio if 'target_position_ratio' in locals() else first_batch_ratio,
                    'current_batch': 1,
                    'first_batch_ratio': first_batch_ratio,
                    'first_buy_price': actual_buy_price,  # 保留首次买入价，用于加仓判断
                    'first_buy_time': trade_time  # 保留首次买入时间
                }
                logger.debug(f"记录第{next_batch_num}批买入: {stock}, {buy_shares}股 @ {actual_buy_price:.2f}元")
            else:
                # 后续批次加仓：添加到批次列表
                if stock in self.position_batches:
                    self.position_batches[stock]['batches'].append(batch_info)
                    self.position_batches[stock]['current_batch'] = next_batch_num
                    logger.debug(f"记录第{next_batch_num}批买入: {stock}, {buy_shares}股 @ {actual_buy_price:.2f}元")
                else:
                    # 异常情况：有持仓但没有批次信息（可能是旧数据）
                    logger.warning(f"{stock}有持仓但无批次信息，创建新的批次追踪")
                    self.position_batches[stock] = {
                        'batches': [batch_info],
                        'target_position': first_batch_ratio if 'first_batch_ratio' in locals() else 0.1,
                        'current_batch': next_batch_num,
                        'first_batch_ratio': first_batch_ratio if 'first_batch_ratio' in locals() else 0.1,
                        'first_buy_price': actual_buy_price,
                        'first_buy_time': trade_time
                    }
            
            logger.debug(f"持仓更新完成: {stock} 总持仓{self.positions[stock]}股, 第{self.position_batches[stock]['current_batch']}批")
            
            # 记录交易
            logger.debug(f"准备创建交易记录: time={trade_time}, stock={stock}, price={price}, actual_price={actual_buy_price}, shares={buy_shares}")
            trade_record = {
                'time': trade_time,
                'stock': stock,
                'direction': 'buy',
                'price': float(actual_buy_price),  # 使用含滑点的实际价格
                'original_price': float(price),     # 记录原始价格
                'slippage_rate': float(slippage_rate),  # 记录滑点率
                'shares': int(buy_shares),
                'amount': float(buy_amount),
                'commission': float(commission),
                'capital': float(self.current_capital),
                'reason': buy_reason
            }
            logger.debug(f"交易记录创建完成: {trade_record}")
            self.trades.append(trade_record)
            logger.debug(f"交易记录已添加到trades列表，当前trades数量: {len(self.trades)}")
            
            logger.debug(f"交易记录已添加: trades数量={len(self.trades)}")
            
            # 获取股票名称
            stock_name = ""
            try:
                detail = xtdata.get_instrument_detail(stock)
                if isinstance(detail, dict):
                    stock_name = detail.get('InstrumentName', '')
            except:
                pass
            display_name = f"{stock} ({stock_name})" if stock_name else stock
            
            # 计算计划和实际占比
            actual_ratio = total_cost / self.initial_capital * 100
            
            # 获取计划仓位比例
            planned_ratio = None
            if hasattr(self, '_planned_position_ratios') and stock in self._planned_position_ratios:
                planned_ratio = self._planned_position_ratios[stock] * 100  # 转换为百分比
            
            # 使用统一的交易输出格式化工具
            reason_text = trade_record['reason']
            output_lines = TradeFormatter.format_buy_output(
                stock_name=display_name,
                trade_time=trade_time,
                price=price,
                actual_price=actual_buy_price,
                slippage_rate=slippage_rate,
                shares=buy_shares,
                amount=buy_amount,
                commission=commission,
                total_cost=total_cost,
                remaining_capital=self.current_capital,
                reason=reason_text,
                planned_ratio=planned_ratio,
                actual_ratio=actual_ratio
            )
            
            # 输出到控制台
            print()  # 换行
            for line in output_lines:
                logger.info(line)
            
            # 同时写入backtest日志文件
            if self.backtest_log_file:
                try:
                    for line in output_lines:
                        self.backtest_log_file.write(line + "\n")
                    self.backtest_log_file.flush()
                except:
                    pass
            
            # ===== 买入后：创建PositionInfo对象并初始化止损 =====
            try:
                # 创建PositionInfo对象
                buy_time = datetime.strptime(trade_time, '%Y-%m-%d %H:%M:%S') if trade_time else datetime.now()
                position_info = PositionInfo(
                    stock=stock,
                    buy_time=buy_time,
                    buy_price=actual_buy_price,
                    shares=buy_shares
                )
                
                # 初始化止损
                stock_data = self.daily_data.get(stock, pd.DataFrame())
                position_info.initialize_stop_loss(stock_data if not stock_data.empty else None)
                
                # 保存到position_batches
                self.position_batches[stock]['position_info'] = position_info
                logger.debug(f"PositionInfo对象已创建并初始化止损: {stock} @ {actual_buy_price:.2f}")
            except Exception as e:
                logger.warning(f"创建PositionInfo对象失败 {stock}: {e}")
            
        except Exception as e:
            # 获取股票名称
            stock_name = ""
            try:
                detail = xtdata.get_instrument_detail(stock)
                if isinstance(detail, dict):
                    stock_name = detail.get('InstrumentName', '')
            except:
                pass
            display_name = f"{stock} ({stock_name})" if stock_name else stock
            logger.error(f"执行买入操作时出错 {display_name}: {str(e)}")
            logger.debug(f"详细错误信息: {repr(e)}", exc_info=True)
            # 重新抛出异常，确保上层能捕获到
            raise

    def _execute_sell(self, stock: str, minute_data, reason: str = "", sell_ratio: float = 1.0, sell_result: Dict = None):
        """
        执行卖出操作
        :param stock: 股票代码
        :param minute_data: 当前分钟数据
        :param reason: 卖出原因
        :param sell_ratio: 卖出比例，1.0表示全部卖出，0.5表示卖出一半（默认1.0）
        """
        try:
            # 检查是否有持仓
            if stock not in self.positions or self.positions[stock] <= 0:
                # 添加调试信息
                logger.debug(f"无法卖出 {stock}: 无持仓或持仓为0 (positions={self.positions.get(stock, 0)})")
                return
                
            # 计算卖出数量（支持分批卖出）
            total_shares = self.positions[stock]
            sell_shares = int(total_shares * sell_ratio)
            # 确保卖出股数是100的整数倍
            sell_shares = (sell_shares // 100) * 100
            
            # 如果计算结果为0但sell_ratio>0，至少卖出100股（如果持仓>=100）
            if sell_shares == 0 and sell_ratio > 0:
                if total_shares >= 100:
                    sell_shares = 100
                else:
                    # 持仓不足100股，全部卖出
                    sell_shares = total_shares
            
            if sell_shares <= 0:
                logger.warning(f"卖出数量计算错误: {stock}, 总持仓{total_shares}, 比例{sell_ratio}, 计算结果{sell_shares}")
                return
            
            # 确保不卖出超过持仓
            sell_shares = min(sell_shares, total_shares)
            is_full_sell = (sell_shares >= total_shares)  # 是否全部卖出
            price = minute_data['close']
            
            # 计算动态滑点率
            daily_data = self.daily_data.get(stock, pd.DataFrame())
            hist_data = {
                'high': daily_data['high'].values if 'high' in daily_data else None,
                'low': daily_data['low'].values if 'low' in daily_data else None,
                'close': daily_data['close'].values if 'close' in daily_data else None
            }
            stock_today_data = getattr(self.strategy_wrapper.strategy_instance, 'today_data', {}).get(stock, {}) if hasattr(self, 'strategy_wrapper') and self.strategy_wrapper else {}
            market_cap = stock_today_data.get('market_cap', 0)
            slippage_rate = self.slippage_calculator.calculate_slippage_rate(
                stock_code=stock,
                current_price=price,
                hist_data=hist_data,
                market_cap=market_cap
            )
            
            # 应用滑点：卖出时价格下降
            actual_sell_price = self.slippage_calculator.apply_slippage(price, slippage_rate, 'sell')
            
            sell_amount = sell_shares * actual_sell_price
            commission = sell_amount * self.commission_rate
            
            # 更新资金和持仓
            self.current_capital += sell_amount - commission
            
            if is_full_sell:
                # 全部卖出：删除持仓记录
                del self.positions[stock]
                
                # 清理批次追踪信息
                if stock in self.position_batches:
                    del self.position_batches[stock]
                    logger.debug(f"全部卖出，清理批次追踪信息: {stock}")
                
                # 同步删除策略的mock_positions（允许再次买入时输出日志）
                if hasattr(self.strategy_wrapper, 'strategy_instance'):
                    if hasattr(self.strategy_wrapper.strategy_instance, 'mock_positions'):
                        if stock in self.strategy_wrapper.strategy_instance.mock_positions:
                            del self.strategy_wrapper.strategy_instance.mock_positions[stock]
                            logger.debug(f"清理策略mock_positions: {stock}")
                
                logger.debug(f"全部卖出完成: {stock}持仓已清空, current_capital={self.current_capital}")
            else:
                # 部分卖出：更新持仓数量
                remaining_shares = total_shares - sell_shares
                self.positions[stock] = remaining_shares
                
                # 从批次列表中删除对应的批次（先进先出FIFO）
                if stock in self.position_batches and 'batches' in self.position_batches[stock]:
                    batches = self.position_batches[stock]['batches']
                    shares_to_remove = sell_shares
                    
                    while shares_to_remove > 0 and batches:
                        first_batch = batches[0]
                        if first_batch['shares'] <= shares_to_remove:
                            # 删除整个批次
                            shares_to_remove -= first_batch['shares']
                            removed_batch = batches.pop(0)
                            logger.debug(f"删除批次: {removed_batch['batch_num']}, {removed_batch['shares']}股")
                        else:
                            # 部分删除
                            first_batch['shares'] -= shares_to_remove
                            logger.debug(f"部分卖出批次{first_batch['batch_num']}: 剩余{first_batch['shares']}股")
                            shares_to_remove = 0
                    
                    # 更新当前批次号
                    if batches:
                        self.position_batches[stock]['current_batch'] = max(b['batch_num'] for b in batches)
                    else:
                        # 如果所有批次都卖完了，清理批次信息
                        del self.position_batches[stock]
                        logger.debug(f"所有批次已卖完，清理批次追踪信息: {stock}")
                
                logger.debug(f"部分卖出完成: {stock} 剩余{remaining_shares}股, current_capital={self.current_capital}")
            
            # 获取交易时间
            trade_time = minute_data.name.strftime('%Y-%m-%d %H:%M:%S')
            
            # 计算盈亏（需要找到对应的买入交易）
            profit = 0
            profit_rate = 0
            hold_days = 0
            buy_price = 0
            
            # 计算持仓加权平均成本（支持多次买入）
            total_buy_cost = 0  # 总买入成本（含手续费）
            total_buy_shares = 0  # 总买入股数
            first_buy_time = None  # 第一次买入时间
            
            # 遍历所有该股票的买入交易，计算加权平均成本
            for trade in self.trades:
                if (trade['stock'] == stock and 
                    trade['direction'] == 'buy' and 
                    trade.get('shares', 0) > 0):
                    total_buy_cost += trade['amount'] + trade['commission']
                    total_buy_shares += trade['shares']
                    if first_buy_time is None:
                        first_buy_time = trade['time']
            
            # 减去之前已卖出的成本（如果有多次卖出）
            total_sell_cost_before = 0  # 之前卖出已分摊的成本
            for trade in self.trades:
                if (trade['stock'] == stock and 
                    trade['direction'] == 'sell'):
                    # 累加之前卖出已分摊的成本
                    if 'sell_cost' in trade:
                        total_sell_cost_before += trade['sell_cost']
            
            # 计算剩余持仓成本和持仓成本价
            remaining_cost = total_buy_cost - total_sell_cost_before
            avg_cost_price = remaining_cost / total_shares if total_shares > 0 else 0  # 持仓成本价
            
            # 计算本次卖出对应的成本（使用加权平均成本）
            sell_cost = avg_cost_price * sell_shares
            
            # 计算盈亏
            profit = (sell_amount - commission) - sell_cost
            profit_rate = (profit / sell_cost) * 100 if sell_cost > 0 else 0
            
            # 用于显示的买入价格（加权平均价格）
            buy_price = avg_cost_price
            
            # 计算持有天数（从当前持仓周期的第一次买入到现在）
            # 修复：对于多次买入卖出循环的股票，应该从当前持仓周期的第一次买入开始计算
            hold_days = 0
            try:
                sell_time = pd.to_datetime(trade_time)
                
                # 找到最近一次全部卖出（清仓）的时间
                last_clear_time = None
                for trade in reversed(self.trades):
                    if (trade['stock'] == stock and 
                        trade['direction'] == 'sell' and
                        trade.get('is_full_sell', False) and
                        pd.to_datetime(trade['time']) < sell_time):
                        last_clear_time = pd.to_datetime(trade['time'])
                        break
                
                # 找到清仓后的第一次买入时间（当前持仓周期的开始）
                current_cycle_first_buy_time = None
                for trade in self.trades:
                    if (trade['stock'] == stock and 
                        trade['direction'] == 'buy' and 
                        trade.get('shares', 0) > 0):
                        buy_time = pd.to_datetime(trade['time'])
                        
                        # 如果找到了清仓时间，买入时间必须在清仓之后
                        if last_clear_time:
                            if buy_time > last_clear_time and buy_time < sell_time:
                                if current_cycle_first_buy_time is None or buy_time < current_cycle_first_buy_time:
                                    current_cycle_first_buy_time = buy_time
                        else:
                            # 没有清仓记录，说明是第一次买入，且必须在卖出之前
                            if buy_time < sell_time:
                                if current_cycle_first_buy_time is None or buy_time < current_cycle_first_buy_time:
                                    current_cycle_first_buy_time = buy_time
                
                # 计算持有天数
                if current_cycle_first_buy_time:
                    hold_days = (sell_time - current_cycle_first_buy_time).days
            except Exception as e:
                logger.warning(f"计算持有天数失败: {stock}, {e}")
                hold_days = 0
            
            # 记录交易
            # 判断是否是强制平仓
            is_forced_close = '强制平仓' in reason or '回测结束' in reason
            
            trade_record = {
                'time': trade_time,
                'stock': stock,
                'direction': 'sell',
                'price': float(actual_sell_price),  # 使用含滑点的实际价格
                'original_price': float(price),      # 记录原始价格
                'slippage_rate': float(slippage_rate),  # 记录滑点率
                'shares': sell_shares,
                'total_shares': total_shares,  # 卖出前的总持仓
                'amount': sell_amount,
                'commission': commission,
                'capital': self.current_capital,
                'reason': reason,
                'profit': profit,
                'profit_rate': profit_rate,
                'hold_days': hold_days,
                'buy_price': buy_price,
                'sell_cost': sell_cost,  # 保存本次卖出分摊的成本，用于多次卖出的成本追踪
                'is_full_sell': is_full_sell,  # 是否全部卖出
                'is_forced_close': is_forced_close  # 是否强制平仓
            }
            self.trades.append(trade_record)
            
            # 获取股票名称
            stock_name = ""
            try:
                detail = xtdata.get_instrument_detail(stock)
                if isinstance(detail, dict):
                    stock_name = detail.get('InstrumentName', '')
            except:
                pass
            display_name = f"{stock} ({stock_name})" if stock_name else stock
            
            # 计算实际卖出比例
            actual_sell_ratio = (sell_shares / total_shares * 100) if total_shares > 0 else 0
            
            # 尝试获取计划卖出比例和触发次数（从position_calculator的sell_ratio）
            planned_sell_ratio = None
            trigger_count = None
            try:
                # 从reason中提取是否是条件A（分批减仓）
                if "条件A" in reason:
                    # 尝试从仓位管理器获取trigger_count
                    if hasattr(self, 'strategy_wrapper') and self.strategy_wrapper:
                        if hasattr(self.strategy_wrapper, 'strategy_instance'):
                            strategy_instance = self.strategy_wrapper.strategy_instance
                            if hasattr(strategy_instance, 'position_manager'):
                                position_obj = strategy_instance.position_manager.get_position(stock)
                                if position_obj:
                                    trigger_count = getattr(position_obj, 'condition_a_count', 1)
                                    # 从配置读取计划减仓比例
                                    from config_loader import POSITION_CALCULATOR_CONFIG
                                    sell_batch_ratios = POSITION_CALCULATOR_CONFIG.get('sell_batch_ratios', {})
                                    batch_keys = ['first', 'second', 'third', 'fourth', 'fifth']
                                    if trigger_count <= len(batch_keys) and batch_keys[trigger_count-1] in sell_batch_ratios:
                                        planned_sell_ratio = sell_batch_ratios[batch_keys[trigger_count-1]] * 100
            except:
                pass
            
            # 使用统一的交易输出格式化工具
            output_lines = TradeFormatter.format_sell_output(
                stock_name=display_name,
                trade_time=trade_time,
                price=price,
                actual_price=actual_sell_price,
                slippage_rate=slippage_rate,
                shares=sell_shares,
                sell_amount=sell_amount,
                commission=commission,
                remaining_capital=self.current_capital,
                reason=reason,
                is_full_sell=is_full_sell,
                planned_sell_ratio=planned_sell_ratio,
                actual_sell_ratio=actual_sell_ratio,
                remaining_shares=total_shares - sell_shares,
                buy_price=buy_price,
                profit=profit,
                profit_rate=profit_rate,
                hold_days=hold_days,
                trigger_count=trigger_count
            )
            
            # 输出到控制台
            print()  # 换行
            for line in output_lines:
                logger.info(line)
            
            # 同时写入backtest日志文件
            if self.backtest_log_file:
                try:
                    for line in output_lines:
                        self.backtest_log_file.write(line + "\n")
                    self.backtest_log_file.flush()
                except:
                    pass
            
            # 技术形态指标状态（只要有today_data就输出，不依赖tech_enhancement）
            if hasattr(self, 'strategy_wrapper') and self.strategy_wrapper:
                today_data = self.strategy_wrapper.strategy_instance.today_data.get(stock, {})
                
                if today_data:
                    # 如果有技术形态增强，显示关键指标
                    if sell_result and sell_result.get('tech_enhancement', 0) > 0:
                        tech_info_parts = []
                        
                        # 从配置文件动态读取评级标准
                        # 获取股票类型（用于选择配置）
                        stock_type = sell_result.get('details', {}).get('stock_type', '大盘股')
                        # 映射到配置key
                        stock_type_map = {'大盘股': 'large_cap', '中盘股': 'medium_cap', '小盘股': 'small_cap'}
                        config_key = stock_type_map.get(stock_type, 'large_cap')
                        
                        # 获取评分标准配置
                        scoring_config = TECHNICAL_PATTERNS_CONFIG.get('upper_shadow', {}).get('sell_signals', {}).get(config_key, {}).get('scoring', {})
                        
                        # 通用评级函数（根据rating_rules自动匹配）
                        def get_rating_by_rules(value, rating_rules, default_label="未知"):
                            """根据评级规则列表，返回匹配的评级标签"""
                            if not rating_rules:
                                return default_label
                            # 按优先级遍历规则（从高到低）
                            for rule in rating_rules:
                                threshold = rule.get('threshold', 0)
                                if value >= threshold:
                                    return rule.get('label', default_label)
                            return default_label
                        
                        # 从today_data获取已计算的指标（避免重复计算）
                        if 'upper_ratio' in today_data:
                            upper_ratio = today_data['upper_ratio']
                            shadow_scoring = scoring_config.get('shadow', {})
                            rating_rules = shadow_scoring.get('rating_rules', [])
                            rating = get_rating_by_rules(upper_ratio, rating_rules, "一般")
                            tech_info_parts.append(f"上影线{upper_ratio:.2%}({rating})")
                        
                        if 'volRatio' in today_data and today_data['volRatio'] > 0:
                            vol_ratio = today_data['volRatio']
                            volume_scoring = scoring_config.get('volume', {})
                            rating_rules = volume_scoring.get('rating_rules', [])
                            rating = get_rating_by_rules(vol_ratio, rating_rules, "缩量")
                            tech_info_parts.append(f"量比{vol_ratio:.2f}({rating})")
                        
                        if 'price_position' in today_data and today_data['price_position'] > 0:
                            price_pos = today_data['price_position']
                            position_scoring = scoring_config.get('position', {})
                            rating_rules = position_scoring.get('rating_rules', [])
                            rating = get_rating_by_rules(price_pos, rating_rules, "中低位")
                            tech_info_parts.append(f"价格位置{price_pos:.2%}({rating})")
                        
                        tech_enhancement = sell_result.get('tech_enhancement', 0)
                        enhancement_config = TECHNICAL_PATTERNS_CONFIG.get('signal_enhancement', {})
                        validation_impact = enhancement_config.get('validation_impact', {})
                        rating_rules = validation_impact.get('rating_rules', [])
                        rating = get_rating_by_rules(tech_enhancement, rating_rules, "一般")
                        tech_info_parts.append(f"增强{tech_enhancement:.1%}({rating})")
                        
                        if tech_info_parts:
                            # 获取卖出时间
                            sell_time = ""
                            time_val = today_data.get('time') or today_data.get('timestamp')
                            if time_val:
                                try:
                                    from datetime import datetime
                                    if isinstance(time_val, (int, float)):
                                        sell_time = datetime.fromtimestamp(time_val).strftime('%H:%M:%S')
                                    else:
                                        sell_time = time_val.strftime('%H:%M:%S')
                                except:
                                    pass
                            
                            if sell_time:
                                logger.info(f"📊 技术形态指标 [{sell_time}]: {' | '.join(tech_info_parts)}")
                            else:
                                logger.info(f"📊 技术形态指标: {' | '.join(tech_info_parts)}")
            
            logger.info("━" * 50)
            
            # ===== 卖出后：清理PositionInfo对象 =====
            # 只有在全部清仓时才清理（PositionInfo会随着position_batches一起删除）
            if is_full_sell and stock in self.position_batches:
                # position_batches在上面_execute_sell中已经被删除，无需额外操作
                logger.debug(f"PositionInfo对象已随position_batches清理: {stock}")
            
        except Exception as e:
            # 获取股票名称
            stock_name = ""
            try:
                detail = xtdata.get_instrument_detail(stock)
                if isinstance(detail, dict):
                    stock_name = detail.get('InstrumentName', '')
            except:
                pass
            display_name = f"{stock} ({stock_name})" if stock_name else stock
            logger.error(f"执行卖出操作时出错 {display_name}: {str(e)}")
            logger.debug(f"详细错误信息: {repr(e)}", exc_info=True)

    def calculate_daily_metrics(self):
        """
        计算每日收益和风险指标
        :return: 每日指标字典
        """
        try:
            if not self.trades:
                return {}
            
            # 按日期分组交易记录
            daily_data = {}
            for trade in self.trades:
                try:
                    # 从时间字符串中提取日期
                    date = trade['time'][:10]  # 提取 'YYYY-MM-DD'
                    if date not in daily_data:
                        daily_data[date] = {
                            'capital': trade['capital'],
                            'trades': []
                        }
                    # 更新为当天最后一笔交易的资产（每笔交易都更新）
                    daily_data[date]['capital'] = trade['capital']
                    daily_data[date]['trades'].append(trade)
                except:
                    continue
            
            # 计算每日指标
            daily_metrics = {}
            previous_capital = self.initial_capital
            
            for date in sorted(daily_data.keys()):
                data = daily_data[date]
                current_capital = data['capital']
                
                # 计算当日收益率
                daily_return = (current_capital - previous_capital) / previous_capital * 100 if previous_capital > 0 else 0
                
                # 计算累计收益率
                cumulative_return = (current_capital - self.initial_capital) / self.initial_capital * 100
                
                # 统计当日交易次数
                trade_count = len(data['trades'])
                
                # 统计当日买入和卖出次数
                buy_count = len([t for t in data['trades'] if t['direction'] == 'buy'])
                sell_count = len([t for t in data['trades'] if t['direction'] == 'sell'])
                
                daily_metrics[date] = {
                    'capital': current_capital,
                    'daily_return': daily_return,
                    'cumulative_return': cumulative_return,
                    'trade_count': trade_count,
                    'buy_count': buy_count,
                    'sell_count': sell_count
                }
                
                previous_capital = current_capital
            
            return daily_metrics
            
        except Exception as e:
            logger.error(f"计算每日指标时出错: {str(e)}")
            logger.debug(f"详细错误信息: {repr(e)}", exc_info=True)
            return {}
   
    def _calculate_daily_capital_usage(self) -> float:
        """
        计算当前持仓的总成本（用于时间加权平均资金使用率计算）
        
        Returns:
            float: 当前持仓的总成本
        """
        total_capital_used = 0
        
        # 遍历所有持仓，累加每个批次的成本
        for stock, batch_info in self.position_batches.items():
            if 'batches' in batch_info:
                for batch in batch_info['batches']:
                    # 批次成本 = 股数 × 成本价
                    shares = batch.get('shares', 0)
                    cost_basis = batch.get('price', 0)  # 使用买入价格作为成本
                    
                    # 累加该批次的成本
                    batch_cost = shares * cost_basis
                    total_capital_used += batch_cost
        
        return total_capital_used

    def print_summary(self, save_to_file=False):
        """打印回测总结（使用统一的TradeAnalyzer）
        
        Args:
            save_to_file: 是否保存到文件，默认True
        """
        if not self.trades:
            logger.warning("无交易记录")
            return

        # 准备日志文件
        log_file = None
        log_filename = None
        
        if save_to_file:
            from datetime import datetime as dt
            import os
            
            # 创建logs目录
            log_dir = "backtest_logs"
            if not os.path.exists(log_dir):
                os.makedirs(log_dir)
            
            # 生成日志文件名：backtest_YYYYMMDD.txt（按日期，追加模式）
            date_str = dt.now().strftime('%Y%m%d')
            log_filename = os.path.join(log_dir, f"backtest_{date_str}.txt")
            log_file = open(log_filename, 'a', encoding='utf-8')
            
            # 写入分隔符和时间戳
            timestamp = dt.now().strftime('%Y-%m-%d %H:%M:%S')
            log_file.write(f"\n{'='*80}\n")
            log_file.write(f"回测时间: {timestamp}\n")
            log_file.write(f"{'='*80}\n\n")
            
            logger.info(f"回测报告将追加保存到: {log_filename}")
        
        # 定义输出函数（同时输出到控制台和文件）
        def output(text=""):
            print(text)
            if log_file:
                log_file.write(text + '\n')

        output()
        output("=" * 80)
        output("📊 分钟级回测报告")
        output("=" * 80)

        # 使用统一的TradeAnalyzer打印所有统计信息
        analyzer = TradeAnalyzer(
            trades=self.trades,
            initial_capital=self.initial_capital,
            current_capital=self.current_capital,
            daily_capital_usage=self.daily_capital_usage  # 传入每日资金占用数据
        )
        
        # 调用统一的print_summary方法
        analyzer.print_summary(
            output_func=output,
            positions=self.positions,
            save_to_file=save_to_file,
            log_file=log_file
        )
        
        # 打印信号验证统计（压缩格式）
        output()
        output("=" * 80)
        output("🔍 信号验证统计")
        output("=" * 80)
        stats = self.signal_checker.get_statistics()
        if stats['total_signals'] > 0:
            # 第一行：总体统计
            time_stats = ' | '.join([f"{k}×{v}" for k, v in stats.get('filter_reasons', {}).items()])
            output(f"总信号: {stats['total_signals']} | 确认: {stats['confirmed_signals']} | "
                   f"过滤: {stats['filtered_signals']} ({stats['filter_rate']:.1%}) | 时段: {time_stats}")
            
            # 第二行：验证器统计
            if stats.get('validator_stats'):
                validator_stats_str = []
                for validator_name, stat in stats['validator_stats'].items():
                    total = stat['passed'] + stat['failed']
                    pass_rate = stat['passed'] / total if total > 0 else 0
                    validator_stats_str.append(f"{validator_name}({stat['passed']}/{total} {pass_rate:.0%})")
                output(f"验证器: {' | '.join(validator_stats_str)}")
        else:
            output("本次回测未触发信号验证（所有信号都在正常时段或无卖出信号）")
        
        # 关闭日志文件
        if log_file:
            log_file.write(f"\n{'='*80}\n")
            log_file.write(f"回测结束\n")
            log_file.write(f"{'='*80}\n\n")
            log_file.close()
            logger.info(f"回测报告已追加保存到: {log_filename}")
        
        # 关闭backtest日志文件
        if self.backtest_log_file:
            try:
                from datetime import datetime as dt
                timestamp = dt.now().strftime('%Y-%m-%d %H:%M:%S')
                self.backtest_log_file.write(f"\n{'='*80}\n")
                self.backtest_log_file.write(f"回测结束: {timestamp}\n")
                self.backtest_log_file.write(f"{'='*80}\n\n")
                self.backtest_log_file.close()
            except:
                pass
            self.backtest_log_file = None

